Skip to content
Open
10 changes: 10 additions & 0 deletions libraries/pet_service/manifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<package>
<name>pet_service</name>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use two spaces for XML indentation.

<version>1.0</version>
<type>service</type>
<dependencies>
<dependency>packet</dependency>
<dependency>shared</dependency>
<dependency>struct</dependency>
</dependencies>
</package>
176 changes: 176 additions & 0 deletions libraries/pet_service/pet_service.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
local math = require('math')
local ffi = require('ffi')

local packet = require('packet')
local server = require('shared.server')
local struct = require('struct')

local item_type = struct.struct({
item_id = {struct.uint16},
raw = {struct.uint8},
})

local data = server.new(struct.struct({
index = {struct.uint16},
id = {struct.uint32},
name = {struct.string(0x10)},
owner_index = {struct.uint16},
owner_id = {struct.uint32},
target_id = {struct.uint32},
hp_percent = {struct.uint8},
mp_percent = {struct.uint8},
tp = {struct.uint32},
active = {struct.bool},
automaton = {struct.struct({
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I dislike this structure being here when no automaton is present. This should be made a pointer and set to a different automaton struct when an automaton is summoned, and set to nil when it is dismissed. This way players could check if an automaton is present with if pet.automaton then.

Unfortunately, I don't think we have a code example for this yet, let me know if you need help implementing it.

active = {struct.bool},
head = {item_type},
frame = {item_type},
attachments = {item_type[12]},
available_heads = {struct.bitfield(4)},
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll have to think about this some more, but I think it would make most sense to make this an array of booleans, then overwrite it in the library to return a set of item IDs. Feel free to discuss this on Discord.

available_frames = {struct.bitfield(4)},
available_attach = {struct.bitfield(32)},
name = {struct.string(0x10)},
hp = {struct.uint16},
hp_max = {struct.uint16},
mp = {struct.uint16},
mp_max = {struct.uint16},
melee = {struct.uint16},
melee_max = {struct.uint16},
ranged = {struct.uint16},
ranged_max = {struct.uint16},
magic = {struct.uint16},
magic_max = {struct.uint16},
str = {struct.uint16},
str_modifier = {struct.uint16},
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should probably discuss the naming here, we don't have this elsewhere yet (though we should add this to the player library as well). In the packet definitions we use stats_base and stats_bonus, but I'm open to suggestions. It should just be the same everywhere.

dex = {struct.uint16},
dex_modifier = {struct.uint16},
vit = {struct.uint16},
vit_modifier = {struct.uint16},
agi = {struct.uint16},
agi_modifier = {struct.uint16},
int = {struct.uint16},
int_modifier = {struct.uint16},
mnd = {struct.uint16},
mnd_modifier = {struct.uint16},
chr = {struct.uint16},
chr_modifier = {struct.uint16},
})}
}))

packet.incoming:register_init({
[{0x037}] = function(p) -- While this packet is mostly player data, it does occassionally update the pet index when no other pet related packet is sent. For example, when moving into zones where the pet is supressed, such as cities and towns, this packet will set the pet index to 0.
data.index = p.pet_index
if p.pet_index and p.pet_index ~= 0 then
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

p.pet_index should never be nil, so the first check could be removed (elsewhere as well).

data.active = true
else
data.active = false
end
end,
[{0x067}] = function(p)
if p.type == 4 then
data.index = p.pet_index
data.id = p.pet_id
data.owner_index = p.owner_index
data.hp_percent = p.hp_percent
data.mp_percent = p.mp_percent
data.tp = p.pet_tp
if p.pet_index and p.pet_index ~= 0 then
data.active = true
else
data.active = false
end
end
end,
[{0x068}] = function(p)
if p.type == 4 then
data.owner_index = p.owner_index
data.owner_id = p.owner_id
data.index = p.pet_index
data.hp_percent = p.hp_percent
data.mp_percent = p.mp_percent
data.tp = p.pet_tp
data.target_id = p.target_id
data.name = p.pet_name
if p.pet_index and p.pet_index ~= 0 then
data.active = true
else
data.active = false
end
end
end,
[{0x044,0x12}] = function(p)

data.automaton.head.raw = p.automaton_head
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As mentioned above, I would remove all raw values and just adjust it here for the item ID. Should also unalign the assignments, keep it consistent with other files and other code segments within this file.

data.automaton.head.item_id = p.automaton_head + 0x2000
data.automaton.frame.raw = p.automaton_frame
data.automaton.frame.item_id = p.automaton_frame + 0x2000
for i=0, 11 do
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Spaces around =.

data.automaton.attachments[i].raw = p.attachments[i]
data.automaton.attachments[i].item_id = p.attachments[i] + 0x2100
end
ffi.copy(data.automaton._available_heads, p._available_heads, 4)
ffi.copy(data.automaton._available_frames, p._available_frames, 4)
ffi.copy(data.automaton._available_attach, p._available_attach, 32)
data.automaton.name = p.pet_name
data.automaton.hp = p.hp
data.automaton.hp_max = p.hp_max
data.automaton.mp = p.mp
data.automaton.mp_max = p.mp_max
data.automaton.melee = p.melee
data.automaton.melee_max = p.melee_max
data.automaton.ranged = p.ranged
data.automaton.ranged_max = p.ranged_max
data.automaton.magic = p.magic
data.automaton.magic_max = p.magic_max
data.automaton.str = p.str
data.automaton.str_modifier = p.str_modifier
data.automaton.dex = p.dex
data.automaton.dex_modifier = p.dex_modifier
data.automaton.vit = p.vit
data.automaton.vit_modifier = p.vit_modifier
data.automaton.agi = p.agi
data.automaton.agi_modifier = p.agi_modifier
data.automaton.int = p.int
data.automaton.int_modifier = p.int_modifier
data.automaton.mnd = p.mnd
data.automaton.mnd_modifier = p.mnd_modifier
data.automaton.chr = p.chr
data.automaton.chr_modifier = p.chr_modifier

local active = data.active and (data.name == data.automaton.name)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't examined this myself, but it feels a bit brittle. Is that the best way to check for an active pet? Can data.automaton.name ever be something other than data.name or empty? If we switch to a pointer based approach this should be moved to the top and the function should just return if it's false.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This compares the automaton's name (pet.automaton.name) to the active pet's name (pet.name) to tell if the automaton is the active pet or not.

This is the only way I managed to think of to check this given the known packet fields, but there might be a better way to tell that we just haven't exposed yet. The client somehow knows the automaton is active and will say "You cannot customize an active automaton." if you try to change attachments. I'm skeptical SE actually left this up to something like comparing the name, but I've looked at the known pet relate packets and I don't see anything else in them that would tell the client the active pet is the PUP automaton...


data.automaton.active = active

if p.hp_max ~= 0 and active then
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And if we do the pointer based approach, this (and the following) check can be removed as well.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I ended up just removing these in b0a3112. I originally did this because there is a slight minimum delay between each 0x044 (about 1sec?). Thinking on it more these detailed HP/MP auto stats seem better left as accurate, if slightly stale, rather than being estimated from low precision percents. This way addons can pick if speed or precision are more important.

data.hp_percent = math.floor(100 * p.hp / p.hp_max)
end
if p.mp_max ~= 0 and active then
data.mp_percent = math.floor(100 * p.mp / p.mp_max)
end
end
})

--[[
Copyright © 2020, John S Hobart
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of the Windower Dev Team nor the
names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE WINDOWER DEV TEAM BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
]]