ArcZoneEvent_BuildFrom (sb.exe 0xC8ED20) builds ArcZoneEvent, ArcZoneResourceEvent (0xC904F0) or ArcZoneStateEvent (0xC952E0); Python read the base fields only for all three and had no save or JSON code. Field names come from each class's ParseKeyword; trigger types and state actions use the client name tables (0x176AF20, 0x176A40C). Base event unknowns are the event-name hash, parent-name hash and has-parent flag; the trigger action's third bool is has_items. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
390 lines
19 KiB
Python
390 lines
19 KiB
Python
# • ▌ ▄ ·. ▄▄▄▄· ▄▄▄ .·▄▄▄▄ ▪ ▄▄▄▄▄ ▄▄▄ ▄▄▄·▄▄▄
|
|
# ·██ ▐███▪▐█ ▀█▪ ▀▄.▀·██▪ ██ ██ •██ ▪ ▀▄ █· ▐█ ▄█▀▄ █·▪
|
|
# ▐█ ▌▐▌▐█·▐█▀▀█▄ ▐▀▀▪▄▐█· ▐█▌▐█· ▐█.▪ ▄█▀▄ ▐▀▀▄ ██▀·▐▀▀▄ ▄█▀▄
|
|
# ██ ██▌▐█▌██▄▪▐█ ▐█▄▄▌██. ██ ▐█▌ ▐█▌·▐█▌.▐▌▐█•█▌ ▐█▪·•▐█•█▌▐█▌.▐▌
|
|
# ▀▀ █▪▀▀▀·▀▀▀▀ ▀▀▀ ▀▀▀▀▀• ▀▀▀ ▀▀▀ ▀█▄▀▪.▀ ▀ .▀ .▀ ▀ ▀█▄▀▪
|
|
# Magicbane Emulator Project © 2013 - 2022
|
|
# www.magicbane.com
|
|
|
|
from collections import OrderedDict
|
|
|
|
from arcane.enums.hashes import hash_to_string, string_to_hash
|
|
from arcane.enums.zone.arc_zone_event import *
|
|
from arcane.util import ResStream
|
|
from .ArcMobile import ArcMobile
|
|
from .ArcProp import ArcProp
|
|
|
|
|
|
def read_hashes(stream: ResStream):
|
|
count = stream.read_dword()
|
|
return [stream.read_dword() for _ in range(count)]
|
|
|
|
|
|
def write_hashes(stream: ResStream, values):
|
|
stream.write_dword(len(values))
|
|
for value in values:
|
|
stream.write_dword(value)
|
|
|
|
|
|
class ArcZTriggerAction:
|
|
def load_binary(self, stream: ResStream):
|
|
self.zone_trigger_action_type = stream.read_long()
|
|
self.zone_trigger_action_unknown1 = stream.read_bool()
|
|
self.zone_trigger_action_is_timed = stream.read_bool()
|
|
self.zone_trigger_action_has_items = stream.read_bool()
|
|
if self.zone_trigger_action_is_timed:
|
|
self.zone_trigger_action_time_begin = stream.read_float()
|
|
self.zone_trigger_action_time_end = stream.read_float()
|
|
self.zone_trigger_action_has_state = stream.read_bool()
|
|
if self.zone_trigger_action_has_state:
|
|
self.zone_trigger_action_new_state = stream.read_long()
|
|
self.zone_trigger_action_specifics = read_hashes(stream)
|
|
self.zone_trigger_action_items = read_hashes(stream)
|
|
self.zone_trigger_action_parent_states = read_hashes(stream)
|
|
|
|
def save_binary(self, stream: ResStream):
|
|
stream.write_long(self.zone_trigger_action_type)
|
|
stream.write_bool(self.zone_trigger_action_unknown1)
|
|
stream.write_bool(self.zone_trigger_action_is_timed)
|
|
stream.write_bool(self.zone_trigger_action_has_items)
|
|
if self.zone_trigger_action_is_timed:
|
|
stream.write_float(self.zone_trigger_action_time_begin)
|
|
stream.write_float(self.zone_trigger_action_time_end)
|
|
stream.write_bool(self.zone_trigger_action_has_state)
|
|
if self.zone_trigger_action_has_state:
|
|
stream.write_long(self.zone_trigger_action_new_state)
|
|
write_hashes(stream, self.zone_trigger_action_specifics)
|
|
write_hashes(stream, self.zone_trigger_action_items)
|
|
write_hashes(stream, self.zone_trigger_action_parent_states)
|
|
|
|
def save_json(self):
|
|
data = OrderedDict()
|
|
data['zone_trigger_action_type'] = TRIGGER_TYPE_TO_STRING[self.zone_trigger_action_type]
|
|
data['zone_trigger_action_unknown1'] = self.zone_trigger_action_unknown1
|
|
data['zone_trigger_action_is_timed'] = self.zone_trigger_action_is_timed
|
|
data['zone_trigger_action_has_items'] = self.zone_trigger_action_has_items
|
|
if self.zone_trigger_action_is_timed:
|
|
data['zone_trigger_action_time_begin'] = self.zone_trigger_action_time_begin
|
|
data['zone_trigger_action_time_end'] = self.zone_trigger_action_time_end
|
|
data['zone_trigger_action_has_state'] = self.zone_trigger_action_has_state
|
|
if self.zone_trigger_action_has_state:
|
|
data['zone_trigger_action_new_state'] = self.zone_trigger_action_new_state
|
|
data['zone_trigger_action_specifics'] = [hash_to_string(h) for h in self.zone_trigger_action_specifics]
|
|
data['zone_trigger_action_items'] = [hash_to_string(h) for h in self.zone_trigger_action_items]
|
|
data['zone_trigger_action_parent_states'] = [hash_to_string(h) for h in self.zone_trigger_action_parent_states]
|
|
return data
|
|
|
|
def load_json(self, data):
|
|
self.zone_trigger_action_type = STRING_TO_TRIGGER_TYPE[data['zone_trigger_action_type']]
|
|
self.zone_trigger_action_unknown1 = data['zone_trigger_action_unknown1']
|
|
self.zone_trigger_action_is_timed = data['zone_trigger_action_is_timed']
|
|
self.zone_trigger_action_has_items = data['zone_trigger_action_has_items']
|
|
if self.zone_trigger_action_is_timed:
|
|
self.zone_trigger_action_time_begin = data['zone_trigger_action_time_begin']
|
|
self.zone_trigger_action_time_end = data['zone_trigger_action_time_end']
|
|
self.zone_trigger_action_has_state = data['zone_trigger_action_has_state']
|
|
if self.zone_trigger_action_has_state:
|
|
self.zone_trigger_action_new_state = data['zone_trigger_action_new_state']
|
|
self.zone_trigger_action_specifics = [string_to_hash(s) for s in data['zone_trigger_action_specifics']]
|
|
self.zone_trigger_action_items = [string_to_hash(s) for s in data['zone_trigger_action_items']]
|
|
self.zone_trigger_action_parent_states = [string_to_hash(s) for s in data['zone_trigger_action_parent_states']]
|
|
|
|
|
|
class ArcZoneEventInfo:
|
|
def load_binary(self, stream: ResStream):
|
|
self.zone_event_recycle = stream.read_long()
|
|
self.zone_event_spawn_radius = stream.read_float()
|
|
self.zone_event_fc_label = stream.read_string()
|
|
self.zone_event_event_name = stream.read_string()
|
|
self.zone_event_id = stream.read_dword()
|
|
self.zone_event_spawn_location = stream.read_tuple()
|
|
self.zone_event_parent_name = stream.read_string()
|
|
self.zone_event_parent_id = stream.read_dword()
|
|
self.zone_event_has_parent = stream.read_bool()
|
|
num_triggers = stream.read_dword()
|
|
self.zone_event_triggers = [ArcZTriggerAction() for _ in range(num_triggers)]
|
|
for action in self.zone_event_triggers:
|
|
action.load_binary(stream)
|
|
|
|
def save_binary(self, stream: ResStream):
|
|
stream.write_long(self.zone_event_recycle)
|
|
stream.write_float(self.zone_event_spawn_radius)
|
|
stream.write_string(self.zone_event_fc_label)
|
|
stream.write_string(self.zone_event_event_name)
|
|
stream.write_dword(self.zone_event_id)
|
|
stream.write_tuple(self.zone_event_spawn_location)
|
|
stream.write_string(self.zone_event_parent_name)
|
|
stream.write_dword(self.zone_event_parent_id)
|
|
stream.write_bool(self.zone_event_has_parent)
|
|
stream.write_dword(len(self.zone_event_triggers))
|
|
for action in self.zone_event_triggers:
|
|
action.save_binary(stream)
|
|
|
|
def save_json(self):
|
|
data = OrderedDict()
|
|
data['zone_event_recycle'] = self.zone_event_recycle
|
|
data['zone_event_spawn_radius'] = self.zone_event_spawn_radius
|
|
data['zone_event_fc_label'] = self.zone_event_fc_label
|
|
data['zone_event_event_name'] = self.zone_event_event_name
|
|
data['zone_event_id'] = self.zone_event_id
|
|
data['zone_event_spawn_location'] = self.zone_event_spawn_location
|
|
data['zone_event_parent_name'] = self.zone_event_parent_name
|
|
data['zone_event_parent_id'] = self.zone_event_parent_id
|
|
data['zone_event_has_parent'] = self.zone_event_has_parent
|
|
data['zone_event_triggers'] = [action.save_json() for action in self.zone_event_triggers]
|
|
return data
|
|
|
|
def load_json(self, data):
|
|
self.zone_event_recycle = data['zone_event_recycle']
|
|
self.zone_event_spawn_radius = data['zone_event_spawn_radius']
|
|
self.zone_event_fc_label = data['zone_event_fc_label']
|
|
self.zone_event_event_name = data['zone_event_event_name']
|
|
self.zone_event_id = data['zone_event_id']
|
|
self.zone_event_spawn_location = data['zone_event_spawn_location']
|
|
self.zone_event_parent_name = data['zone_event_parent_name']
|
|
self.zone_event_parent_id = data['zone_event_parent_id']
|
|
self.zone_event_has_parent = data['zone_event_has_parent']
|
|
self.zone_event_triggers = []
|
|
for action_data in data['zone_event_triggers']:
|
|
action = ArcZTriggerAction()
|
|
action.load_json(action_data)
|
|
self.zone_event_triggers.append(action)
|
|
|
|
|
|
class ArcZoneResourceEventInfo(ArcZoneEventInfo):
|
|
def load_binary(self, stream: ResStream):
|
|
super().load_binary(stream)
|
|
self.resource_event_max_spawns = stream.read_long()
|
|
self.resource_event_ultimate_limit = stream.read_long()
|
|
self.resource_event_respawn_delay = stream.read_long()
|
|
self.resource_event_items_per_spawn = stream.read_long()
|
|
self.resource_event_random_time = stream.read_long()
|
|
self.resource_event_auto = stream.read_bool()
|
|
self.resource_event_timed = stream.read_bool()
|
|
self.resource_event_possible = stream.read_bool()
|
|
num_item_spawns = stream.read_dword()
|
|
self.resource_event_item_spawns = [[
|
|
stream.read_qword(),
|
|
stream.read_float(),
|
|
] for _ in range(num_item_spawns)]
|
|
|
|
def save_binary(self, stream: ResStream):
|
|
super().save_binary(stream)
|
|
stream.write_long(self.resource_event_max_spawns)
|
|
stream.write_long(self.resource_event_ultimate_limit)
|
|
stream.write_long(self.resource_event_respawn_delay)
|
|
stream.write_long(self.resource_event_items_per_spawn)
|
|
stream.write_long(self.resource_event_random_time)
|
|
stream.write_bool(self.resource_event_auto)
|
|
stream.write_bool(self.resource_event_timed)
|
|
stream.write_bool(self.resource_event_possible)
|
|
stream.write_dword(len(self.resource_event_item_spawns))
|
|
for item_id, chance in self.resource_event_item_spawns:
|
|
stream.write_qword(item_id)
|
|
stream.write_float(chance)
|
|
|
|
def save_json(self):
|
|
data = super().save_json()
|
|
data['resource_event_max_spawns'] = self.resource_event_max_spawns
|
|
data['resource_event_ultimate_limit'] = self.resource_event_ultimate_limit
|
|
data['resource_event_respawn_delay'] = self.resource_event_respawn_delay
|
|
data['resource_event_items_per_spawn'] = self.resource_event_items_per_spawn
|
|
data['resource_event_random_time'] = self.resource_event_random_time
|
|
data['resource_event_auto'] = self.resource_event_auto
|
|
data['resource_event_timed'] = self.resource_event_timed
|
|
data['resource_event_possible'] = self.resource_event_possible
|
|
data['resource_event_item_spawns'] = self.resource_event_item_spawns
|
|
return data
|
|
|
|
def load_json(self, data):
|
|
super().load_json(data)
|
|
self.resource_event_max_spawns = data['resource_event_max_spawns']
|
|
self.resource_event_ultimate_limit = data['resource_event_ultimate_limit']
|
|
self.resource_event_respawn_delay = data['resource_event_respawn_delay']
|
|
self.resource_event_items_per_spawn = data['resource_event_items_per_spawn']
|
|
self.resource_event_random_time = data['resource_event_random_time']
|
|
self.resource_event_auto = data['resource_event_auto']
|
|
self.resource_event_timed = data['resource_event_timed']
|
|
self.resource_event_possible = data['resource_event_possible']
|
|
self.resource_event_item_spawns = data['resource_event_item_spawns']
|
|
|
|
|
|
class EventStateInfo:
|
|
def load_binary(self, stream: ResStream):
|
|
self.state_action = stream.read_long()
|
|
self.state_index = stream.read_long()
|
|
self.state_new_faction = stream.read_bool()
|
|
self.state_new_goal = stream.read_bool()
|
|
self.state_use_goal = stream.read_bool()
|
|
self.state_name = stream.read_dword()
|
|
self.state_goal = stream.read_dword()
|
|
self.state_faction = stream.read_dword()
|
|
self.state_has_auto_state = stream.read_bool()
|
|
if self.state_has_auto_state:
|
|
self.state_auto_state = [stream.read_long() for _ in range(3)]
|
|
self.state_has_death_trigger = stream.read_bool()
|
|
if self.state_has_death_trigger:
|
|
self.state_death_trigger = stream.read_dword()
|
|
self.state_trigger_only = stream.read_bool()
|
|
self.state_max_mob_spawns = stream.read_long()
|
|
|
|
def save_binary(self, stream: ResStream):
|
|
stream.write_long(self.state_action)
|
|
stream.write_long(self.state_index)
|
|
stream.write_bool(self.state_new_faction)
|
|
stream.write_bool(self.state_new_goal)
|
|
stream.write_bool(self.state_use_goal)
|
|
stream.write_dword(self.state_name)
|
|
stream.write_dword(self.state_goal)
|
|
stream.write_dword(self.state_faction)
|
|
stream.write_bool(self.state_has_auto_state)
|
|
if self.state_has_auto_state:
|
|
for value in self.state_auto_state:
|
|
stream.write_long(value)
|
|
stream.write_bool(self.state_has_death_trigger)
|
|
if self.state_has_death_trigger:
|
|
stream.write_dword(self.state_death_trigger)
|
|
stream.write_bool(self.state_trigger_only)
|
|
stream.write_long(self.state_max_mob_spawns)
|
|
|
|
def save_json(self):
|
|
data = OrderedDict()
|
|
data['state_action'] = STATE_ACTION_TO_STRING[self.state_action]
|
|
data['state_index'] = self.state_index
|
|
data['state_new_faction'] = self.state_new_faction
|
|
data['state_new_goal'] = self.state_new_goal
|
|
data['state_use_goal'] = self.state_use_goal
|
|
data['state_name'] = hash_to_string(self.state_name)
|
|
data['state_goal'] = hash_to_string(self.state_goal)
|
|
data['state_faction'] = hash_to_string(self.state_faction)
|
|
data['state_has_auto_state'] = self.state_has_auto_state
|
|
if self.state_has_auto_state:
|
|
data['state_auto_state'] = self.state_auto_state
|
|
data['state_has_death_trigger'] = self.state_has_death_trigger
|
|
if self.state_has_death_trigger:
|
|
data['state_death_trigger'] = hash_to_string(self.state_death_trigger)
|
|
data['state_trigger_only'] = self.state_trigger_only
|
|
data['state_max_mob_spawns'] = self.state_max_mob_spawns
|
|
return data
|
|
|
|
def load_json(self, data):
|
|
self.state_action = STRING_TO_STATE_ACTION[data['state_action']]
|
|
self.state_index = data['state_index']
|
|
self.state_new_faction = data['state_new_faction']
|
|
self.state_new_goal = data['state_new_goal']
|
|
self.state_use_goal = data['state_use_goal']
|
|
self.state_name = string_to_hash(data['state_name'])
|
|
self.state_goal = string_to_hash(data['state_goal'])
|
|
self.state_faction = string_to_hash(data['state_faction'])
|
|
self.state_has_auto_state = data['state_has_auto_state']
|
|
if self.state_has_auto_state:
|
|
self.state_auto_state = data['state_auto_state']
|
|
self.state_has_death_trigger = data['state_has_death_trigger']
|
|
if self.state_has_death_trigger:
|
|
self.state_death_trigger = string_to_hash(data['state_death_trigger'])
|
|
self.state_trigger_only = data['state_trigger_only']
|
|
self.state_max_mob_spawns = data['state_max_mob_spawns']
|
|
|
|
|
|
class ArcZoneStateEventInfo(ArcZoneEventInfo):
|
|
def load_binary(self, stream: ResStream):
|
|
super().load_binary(stream)
|
|
self.state_event_default_state = stream.read_long()
|
|
self.state_event_require_recycle = stream.read_bool()
|
|
num_states = stream.read_dword()
|
|
self.state_event_states = [EventStateInfo() for _ in range(num_states)]
|
|
for state in self.state_event_states:
|
|
state.load_binary(stream)
|
|
num_props = stream.read_dword()
|
|
self.state_event_props = []
|
|
for _ in range(num_props):
|
|
state_num = stream.read_long()
|
|
prop = ArcProp()
|
|
prop.load_binary(stream)
|
|
self.state_event_props.append([state_num, prop])
|
|
num_mobiles = stream.read_dword()
|
|
self.state_event_mobiles = []
|
|
for _ in range(num_mobiles):
|
|
state_num = stream.read_long()
|
|
mobile = ArcMobile()
|
|
mobile.load_binary(stream)
|
|
self.state_event_mobiles.append([state_num, mobile])
|
|
|
|
def save_binary(self, stream: ResStream):
|
|
super().save_binary(stream)
|
|
stream.write_long(self.state_event_default_state)
|
|
stream.write_bool(self.state_event_require_recycle)
|
|
stream.write_dword(len(self.state_event_states))
|
|
for state in self.state_event_states:
|
|
state.save_binary(stream)
|
|
stream.write_dword(len(self.state_event_props))
|
|
for state_num, prop in self.state_event_props:
|
|
stream.write_long(state_num)
|
|
prop.save_binary(stream)
|
|
stream.write_dword(len(self.state_event_mobiles))
|
|
for state_num, mobile in self.state_event_mobiles:
|
|
stream.write_long(state_num)
|
|
mobile.save_binary(stream)
|
|
|
|
def save_json(self):
|
|
data = super().save_json()
|
|
data['state_event_default_state'] = self.state_event_default_state
|
|
data['state_event_require_recycle'] = self.state_event_require_recycle
|
|
data['state_event_states'] = [state.save_json() for state in self.state_event_states]
|
|
data['state_event_props'] = [[state_num, prop.save_json()] for state_num, prop in self.state_event_props]
|
|
data['state_event_mobiles'] = [[state_num, mobile.save_json()] for state_num, mobile in self.state_event_mobiles]
|
|
return data
|
|
|
|
def load_json(self, data):
|
|
super().load_json(data)
|
|
self.state_event_default_state = data['state_event_default_state']
|
|
self.state_event_require_recycle = data['state_event_require_recycle']
|
|
self.state_event_states = []
|
|
for state_data in data['state_event_states']:
|
|
state = EventStateInfo()
|
|
state.load_json(state_data)
|
|
self.state_event_states.append(state)
|
|
self.state_event_props = []
|
|
for state_num, prop_data in data['state_event_props']:
|
|
prop = ArcProp()
|
|
prop.load_json(prop_data)
|
|
self.state_event_props.append([state_num, prop])
|
|
self.state_event_mobiles = []
|
|
for state_num, mobile_data in data['state_event_mobiles']:
|
|
mobile = ArcMobile()
|
|
mobile.load_json(mobile_data)
|
|
self.state_event_mobiles.append([state_num, mobile])
|
|
|
|
|
|
class ArcZoneEvent:
|
|
def load_binary(self, stream: ResStream):
|
|
self.zone_event_type = stream.read_dword()
|
|
|
|
if self.zone_event_type == EVENT:
|
|
self.zone_event_data = ArcZoneEventInfo()
|
|
elif self.zone_event_type == RESOURCE:
|
|
self.zone_event_data = ArcZoneResourceEventInfo()
|
|
elif self.zone_event_type == STATE:
|
|
self.zone_event_data = ArcZoneStateEventInfo()
|
|
self.zone_event_data.load_binary(stream)
|
|
|
|
def save_binary(self, stream: ResStream):
|
|
stream.write_dword(self.zone_event_type)
|
|
self.zone_event_data.save_binary(stream)
|
|
|
|
def save_json(self):
|
|
data = OrderedDict()
|
|
data['zone_event_type'] = ZONE_EVENT_TO_STRING[self.zone_event_type]
|
|
data['zone_event_data'] = self.zone_event_data.save_json()
|
|
return data
|
|
|
|
def load_json(self, data):
|
|
self.zone_event_type = STRING_TO_ZONE_EVENT[data['zone_event_type']]
|
|
if self.zone_event_type == EVENT:
|
|
self.zone_event_data = ArcZoneEventInfo()
|
|
elif self.zone_event_type == RESOURCE:
|
|
self.zone_event_data = ArcZoneResourceEventInfo()
|
|
elif self.zone_event_type == STATE:
|
|
self.zone_event_data = ArcZoneStateEventInfo()
|
|
self.zone_event_data.load_json(data['zone_event_data'])
|