start describing enemy types

This commit is contained in:
Martin Michelsen
2025-03-26 23:07:39 -07:00
parent ad51dcf16f
commit 936b914cbc
8 changed files with 403 additions and 260 deletions
+1 -1
View File
@@ -76,7 +76,7 @@ There is a lot of code in this project that could be useful as a reference. Some
* **src/ItemData.hh**: Item format reference * **src/ItemData.hh**: Item format reference
* **src/ItemCreator.hh/cc**: Reverse-engineered item generator from Episodes 1&2 (used for all versions) * **src/ItemCreator.hh/cc**: Reverse-engineered item generator from Episodes 1&2 (used for all versions)
* **src/ItemParameterTable.hh**: Format of many structures in ItemPMT.prs * **src/ItemParameterTable.hh**: Format of many structures in ItemPMT.prs
* **src/Map.hh/cc**: Map file (.dat) structure and reverse-engineered Challenge Mode random enemy generation algorithm * **src/Map.hh/cc**: Map file (.dat) structure, listing of object/enemy types and parameters, and reverse-engineered Challenge Mode random enemy generation algorithm
* **src/QuestScript.cc**: Complete listing of all quest opcodes on all versions, along with their arguments and behavior * **src/QuestScript.cc**: Complete listing of all quest opcodes on all versions, along with their arguments and behavior
* **src/RareItemSet.hh/cc**: Format of ItemRT files (rare item drop tables) * **src/RareItemSet.hh/cc**: Format of ItemRT files (rare item drop tables)
* **src/SaveFileFormats.hh**: Definitions of save file structures for all versions * **src/SaveFileFormats.hh**: Definitions of save file structures for all versions
+41 -15
View File
@@ -2794,18 +2794,12 @@ ChatCommandDefinition cc_whatobj(
layout_var = 0; layout_var = 0;
} }
float min_dist2 = 0.0f; double min_dist2 = -1.0;
VectorXYZF nearest_worldspace_pos; VectorXYZF nearest_worldspace_pos;
shared_ptr<const MapState::ObjectState> nearest_obj; shared_ptr<const MapState::ObjectState> nearest_obj;
for (const auto& it : l->map_state->iter_object_states(a.c->version())) { shared_ptr<const MapState::EnemyState> nearest_ene;
if (!it->super_obj || (it->super_obj->floor != a.c->floor)) {
continue;
}
const auto& def = it->super_obj->version(a.c->version());
if (!def.set_entry) {
continue;
}
auto check_entity = [&](auto& nearest_entity, auto entity, const auto& def) -> void {
VectorXYZF worldspace_pos; VectorXYZF worldspace_pos;
if (l->episode != Episode::EP3) { if (l->episode != Episode::EP3) {
try { try {
@@ -2821,23 +2815,55 @@ ChatCommandDefinition cc_whatobj(
} }
float dist2 = (VectorXZF(worldspace_pos) - a.c->pos).norm2(); float dist2 = (VectorXZF(worldspace_pos) - a.c->pos).norm2();
if (!nearest_obj || (dist2 < min_dist2)) { if ((min_dist2 < 0.0) || (dist2 < min_dist2)) {
nearest_obj = it; nearest_entity = entity;
nearest_worldspace_pos = worldspace_pos; nearest_worldspace_pos = worldspace_pos;
min_dist2 = dist2; min_dist2 = dist2;
} }
};
for (const auto& it : l->map_state->iter_object_states(a.c->version())) {
if (it->super_obj && (it->super_obj->floor == a.c->floor)) {
const auto& def = it->super_obj->version(a.c->version());
if (def.set_entry) {
check_entity(nearest_obj, it, def);
}
}
}
for (const auto& it : l->map_state->iter_enemy_states(a.c->version())) {
if (it->super_ene && (it->super_ene->floor == a.c->floor)) {
const auto& def = it->super_ene->version(a.c->version());
if (def.set_entry) {
check_entity(nearest_ene, it, def);
}
}
} }
if (!nearest_obj) { // Since we check all objects first, nearest_ene will only be set if
throw precondition_failed("$C4No objects nearby"); // there is an enemy closer than all objects. So, we print that if it's
} else { // set, and print the object if not.
send_text_message_printf(a.c, "$C5K-%03zX\n$C6%s\nX:%.2f Z:%.2f", if (nearest_ene) {
const auto* set_entry = nearest_ene->super_ene->version(a.c->version()).set_entry;
send_text_message_printf(a.c, "$C5E-%03zX\n$C6%s\n$C2%s\n$C7X:%.2f Z:%.2f",
nearest_ene->e_id, phosg::name_for_enum(nearest_ene->type(a.c->version(), l->episode, l->event)),
MapFile::name_for_enemy_type(set_entry->base_type),
nearest_worldspace_pos.x.load(), nearest_worldspace_pos.z.load());
auto set_str = set_entry->str();
a.c->log.info("Enemy found via $whatobj: E-%03zX %s at x=%g y=%g z=%g",
nearest_ene->e_id, set_str.c_str(),
nearest_worldspace_pos.x.load(), nearest_worldspace_pos.y.load(), nearest_worldspace_pos.z.load());
} else if (nearest_obj) {
send_text_message_printf(a.c, "$C5K-%03zX\n$C6%s\n$C7X:%.2f Z:%.2f",
nearest_obj->k_id, nearest_obj->type_name(a.c->version()), nearest_obj->k_id, nearest_obj->type_name(a.c->version()),
nearest_worldspace_pos.x.load(), nearest_worldspace_pos.z.load()); nearest_worldspace_pos.x.load(), nearest_worldspace_pos.z.load());
auto set_str = nearest_obj->super_obj->version(a.c->version()).set_entry->str(); auto set_str = nearest_obj->super_obj->version(a.c->version()).set_entry->str();
a.c->log.info("Object found via $whatobj: K-%03zX %s at x=%g y=%g z=%g", a.c->log.info("Object found via $whatobj: K-%03zX %s at x=%g y=%g z=%g",
nearest_obj->k_id, set_str.c_str(), nearest_obj->k_id, set_str.c_str(),
nearest_worldspace_pos.x.load(), nearest_worldspace_pos.y.load(), nearest_worldspace_pos.z.load()); nearest_worldspace_pos.x.load(), nearest_worldspace_pos.y.load(), nearest_worldspace_pos.z.load());
} else {
throw precondition_failed("$C4No objects or\nenemies are nearby");
} }
}, },
unavailable_on_proxy_server); unavailable_on_proxy_server);
+6 -4
View File
@@ -5546,10 +5546,12 @@ struct G_RevivePlayer_V3_BB_6xA1 {
// server on BB) // server on BB)
struct G_SpecializableItemDropRequest_6xA2 : G_StandardDropItemRequest_PC_V3_BB_6x60 { struct G_SpecializableItemDropRequest_6xA2 : G_StandardDropItemRequest_PC_V3_BB_6x60 {
/* 18 */ le_float fparam3 = 0.0f; // These fields directly map to param3-6 in the ObjectSetEntry structure from
/* 1C */ le_int32_t iparam4 = 0; // which the box was created.
/* 20 */ le_int32_t iparam5 = 0; /* 18 */ le_float param3 = 0.0f;
/* 24 */ le_int32_t iparam6 = 0; /* 1C */ le_int32_t param4 = 0;
/* 20 */ le_int32_t param5 = 0;
/* 24 */ le_int32_t param6 = 0;
/* 28 */ /* 28 */
} __packed_ws__(G_SpecializableItemDropRequest_6xA2, 0x28); } __packed_ws__(G_SpecializableItemDropRequest_6xA2, 0x28);
+320 -205
View File
@@ -573,6 +573,18 @@ static const vector<vector<vector<AreaMapFileInfo>>> map_file_info = {
const char* MapFile::name_for_object_type(uint16_t type) { const char* MapFile::name_for_object_type(uint16_t type) {
static const unordered_map<uint16_t, const char*> names({ static const unordered_map<uint16_t, const char*> names({
// This is newserv's canonical definition of map object types. Enemy and
// NPC types are documented in name_for_enemy_type instead.
// Objects defined in map files take arguments in the form of an
// ObjectSetEntry structure (see Map.hh). Most objects take parameters
// only in param1-3 (floats) and param4-6 (ints), but a few of them use
// the angle fields as additional int parameters. All objects are
// available on all versions of the game (except Episode 3) unless
// otherwise noted, but most objects are available only on specific
// floors unless an omnispawn patch is used. (TODO: Add default floor
// availability information in the notes here.)
// Defines where a player should start when entering a floor. Params: // Defines where a player should start when entering a floor. Params:
// param1 = client ID // param1 = client ID
// param4 = source type: // param4 = source type:
@@ -1049,9 +1061,8 @@ const char* MapFile::name_for_object_type(uint16_t type) {
// taken (ignored if param6 is nonzero) // taken (ignored if param6 is nonzero)
// param4 = number of hits required to activate, minus 1 (so e.g. a // param4 = number of hits required to activate, minus 1 (so e.g. a
// value of 4 here means 5 hits needed) // value of 4 here means 5 hits needed)
// param5 = if in the range [100, 999], uses the default free play // param5 = object number (if outside the range [100, 999], uses the
// script instead of the loaded quest? (TODO: verify this; see // free play script when looking up param6 instead of the quest)
// TOAttackableCol_on_attack for usage)
// param6 = quest label to call when required number of hits is taken // param6 = quest label to call when required number of hits is taken
// (if zero, switch flag in param3 is set instead) // (if zero, switch flag in param3 is set instead)
{0x0023, "TOAttackableCol"}, {0x0023, "TOAttackableCol"},
@@ -2262,9 +2273,8 @@ const char* MapFile::name_for_object_type(uint16_t type) {
// Like TObjQuestColA (TODO: In what ways is it different?). Parameters // Like TObjQuestColA (TODO: In what ways is it different?). Parameters
// are the same as for TObjQuestCol, but also: // are the same as for TObjQuestCol, but also:
// param2 = TODO // param2 = TODO
// param5 = quest script manager to use? (TODO): // param5 = quest script manager to use (zero or negative = quest,
// zero or negative = online // positive = free play)
// positive = offline
// Availability: v3+ only // Availability: v3+ only
{0x02B8, "TObjQuestColALock2"}, {0x02B8, "TObjQuestColALock2"},
@@ -2608,130 +2618,235 @@ const char* MapFile::name_for_object_type(uint16_t type) {
const char* MapFile::name_for_enemy_type(uint16_t type) { const char* MapFile::name_for_enemy_type(uint16_t type) {
static const unordered_map<uint16_t, const char*> names({ static const unordered_map<uint16_t, const char*> names({
{0x0001, "TObjNpcFemaleBase"}, // This is newserv's canonical definition of map enemy and NPC types.
{0x0002, "TObjNpcFemaleChild"}, // Object types are documented in name_for_object_type instead.
{0x0003, "TObjNpcFemaleDwarf"},
{0x0004, "TObjNpcFemaleFat"}, // Enemies and NPCs take a similar arguments structure as objects:
{0x0005, "TObjNpcFemaleMacho"}, // objects use ObjectSetEntry, enemies use EnemySetEntry. Unlike objects,
{0x0006, "TObjNpcFemaleOld"}, // some IDs are reused across game versions, so the same ID can generate
{0x0007, "TObjNpcFemaleTall"}, // a completely different entity on different game versions. Where this
{0x0008, "TObjNpcMaleBase"}, // happens is noted in the comments below.
{0x0009, "TObjNpcMaleChild"},
{0x000A, "TObjNpcMaleDwarf"}, // TODO: Add default floor availability information in the notes here.
{0x000B, "TObjNpcMaleFat"},
{0x000C, "TObjNpcMaleMacho"}, // NPCs. Params:
{0x000D, "TObjNpcMaleOld"}, // param1 = max walk distance from home (ignored if param6 == 0)
{0x000E, "TObjNpcMaleTall"}, // param2 = visibility register number (if this is > 0, the NPC will
{0x0019, "TObjNpcSoldierBase"}, // only be visible when this register is nonzero; if this is >= 1000,
{0x001A, "TObjNpcSoldierMacho"}, // the effective register is param2 - 1000 and register values for
{0x001B, "TObjNpcGovernorBase"}, // both param2 and param3 are read from the free play script
{0x001C, "TObjNpcConnoisseur"}, // environment instead of the quest script environment)
{0x001D, "TObjNpcCloakroomBase"}, // param3 = hide override register number (if this is > 0, the NPC will
{0x001E, "TObjNpcExpertBase"}, // not be visible when this register is nonzero, regardless of the
{0x001F, "TObjNpcNurseBase"}, // state of the register specified by param2; if this is >= 1000, the
{0x0020, "TObjNpcSecretaryBase"}, // effective register is param3 - 1000 and register values for both
{0x0021, "TObjNpcHHM00"}, // param2 and param3 are read from the free play script environment
{0x0022, "TObjNpcNHW00"}, // instead of the quest script environment)
{0x0024, "TObjNpcHRM00"}, // param4 = object number ("character ID" in qedit; if this is outside
{0x0025, "TObjNpcARM00"}, // the range [100, 999], the quest label in param5 is called in the
{0x0026, "TObjNpcARW00"}, // free play script instead of the quest script)
{0x0027, "TObjNpcHFW00"}, // param5 = quest label to call when interacted with (if zero, NPC does
{0x0028, "TObjNpcNFM00"}, // nothing upon interaction)
{0x0029, "TObjNpcNFW00"}, // param6 = if nonzero, NPC walks around; if zero, stands still
{0x002B, "TObjNpcNHW01"}, // TODO: setting param4 to 0 changes something else about the NPC, figure
{0x002C, "TObjNpcAHM01"}, // out what this does (see TObjNpcBase_v57_set_config_from_params)
{0x002D, "TObjNpcHRM01"}, {0x0001, "TObjNpcFemaleBase"}, // Woman with red hair and purple outfit
{0x0030, "TObjNpcHFW01"}, {0x0002, "TObjNpcFemaleChild"}, // Shorter version of the above
{0x0031, "TObjNpcNFM01"}, {0x0003, "TObjNpcFemaleDwarf"}, // Woman wearing green outfit
{0x0032, "TObjNpcNFW01"}, {0x0004, "TObjNpcFemaleFat"}, // Woman outside Hunter's Guild
{0x0033, "TObjNpcEnemy"}, // v3+ only {0x0005, "TObjNpcFemaleMacho"}, // Tool shop woman
{0x0006, "TObjNpcFemaleOld"}, // Older woman with yellow/red outfit
{0x0007, "TObjNpcFemaleTall"}, // Woman walking around inside shop area
{0x0008, "TObjNpcMaleBase"}, // Similar appearance to weapon shop man
{0x0009, "TObjNpcMaleChild"}, // Kid wearing purple
{0x000A, "TObjNpcMaleDwarf"}, // Man outside Medical Center
{0x000B, "TObjNpcMaleFat"}, // Armor shop man
{0x000C, "TObjNpcMaleMacho"}, // Weapon shop man
{0x000D, "TObjNpcMaleOld"}, // Man near telepipe locations
{0x000E, "TObjNpcMaleTall"}, // Man wearing turquoise
{0x0019, "TObjNpcSoldierBase"}, // Man right of the Ragol warp door
{0x001A, "TObjNpcSoldierMacho"}, // Man left of the Ragol warp door
{0x001B, "TObjNpcGovernorBase"}, // Principal Tyrell
{0x001C, "TObjNpcConnoisseur"}, // Tekker
{0x001D, "TObjNpcCloakroomBase"}, // Bank woman
{0x001E, "TObjNpcExpertBase"}, // Man in front of bank
{0x001F, "TObjNpcNurseBase"}, // Nurses in Medical Center
{0x0020, "TObjNpcSecretaryBase"}, // Irene
{0x0021, "TObjNpcHHM00"}, // TODO
{0x0022, "TObjNpcNHW00"}, // TODO
{0x0024, "TObjNpcHRM00"}, // TODO
{0x0025, "TObjNpcARM00"}, // TODO
{0x0026, "TObjNpcARW00"}, // TODO
{0x0027, "TObjNpcHFW00"}, // TODO
{0x0028, "TObjNpcNFM00"}, // TODO
{0x0029, "TObjNpcNFW00"}, // TODO
{0x002B, "TObjNpcNHW01"}, // TODO
{0x002C, "TObjNpcAHM01"}, // TODO
{0x002D, "TObjNpcHRM01"}, // TODO
{0x0030, "TObjNpcHFW01"}, // TODO
{0x0031, "TObjNpcNFM01"}, // TODO
{0x0032, "TObjNpcNFW01"}, // TODO
{0x0045, "TObjNpcLappy"}, // Rappy
{0x0046, "TObjNpcMoja"}, // Small Hildebear
{0x0047, "TObjNpcRico"}, // Rico (v2 only; not available on v1 or v3+)
{0x00A9, "TObjNpcBringer"}, // Dark Bringer
{0x00D0, "TObjNpcKenkyu"}, // TODO (v3+ only)
{0x00D1, "TObjNpcSoutokufu"}, // TODO (v3+ only)
{0x00D2, "TObjNpcHosa"}, // TODO (v3+ only)
{0x00D3, "TObjNpcKenkyuW"}, // TODO (v3+ only)
{0x00F0, "TObjNpcHosa2"}, // TODO (v3+ only)
{0x00F1, "TObjNpcKenkyu2"}, // TODO (v3+ only)
{0x00F2, "TObjNpcNgcBase(0x00F2)"}, // TODO (v3+ only)
{0x00F3, "TObjNpcNgcBase(0x00F3)"}, // TODO (v3+ only)
{0x00F4, "TObjNpcNgcBase(0x00F4)"}, // TODO (v3+ only)
{0x00F5, "TObjNpcNgcBase(0x00F5)"}, // TODO (v3+ only)
{0x00F6, "TObjNpcNgcBase(0x00F6)"}, // TODO (v3+ only)
{0x00F7, "TObjNpcNgcBase(0x00F7)"}, // TODO (v3+ only)
{0x00F8, "TObjNpcNgcBase(0x00F8)"}, // TODO (v3+ only)
{0x00F9, "TObjNpcNgcBase(0x00F9)"}, // TODO (v3+ only)
{0x00FA, "TObjNpcNgcBase(0x00FA)"}, // TODO (v3+ only)
{0x00FB, "TObjNpcNgcBase(0x00FB)"}, // TODO (v3+ only)
{0x00FC, "TObjNpcNgcBase(0x00FC)"}, // Man in room next to Ep2 Hunter's Guild (v3+ only)
{0x00FD, "TObjNpcNgcBase(0x00FD)"}, // TODO (v3+ only)
{0x00FE, "TObjNpcNgcBase(0x00FE)"}, // TODO (v3+ only)
{0x00FF, "TObjNpcNgcBase(0x00FF)"}, // TODO (v3+ only)
// Enemy that behaves like an NPC. Has all the same params as the above
// NPC types, but also:
// angle.x = definition index
// The definition index is an integer from 0 to 15 (decimal) specifying
// which model, animations, and hitbox to use. The available choices
// depend on which assets are loaded, which in turn depend on the area
// the NPC appears in.
// TODO: Make a list of all of the choices for each area here
// Availability: v3+ only
{0x0033, "TObjNpcEnemy"},
// Hildebear. Params:
// param1 = initial location (clamped to [0, 1]; 0 = ground, 1 = sky)
// param2 = TODO (value used is param2 + 0.3, clamped to [0, 1]; could
// be an input to monster AI)
// param3 = TODO (value used is param3 + 0.6, clamped to [0, 1]; could
// be an input to monster AI)
// param6 = if >= 1, always rare
{0x0040, "TObjEneMoja"}, {0x0040, "TObjEneMoja"},
// Rappy. Params:
// param1 = TODO (clamped to [0, 1]; overwritten with 1 if wave_number
// is > 0; could be spawn location like for Hildebear?)
// param6 = rare flag (on v1-v3, rappy is rare if param6 != 0; on v4,
// rappy is rare if (param6 & 1) != 0)
// param7 = TODO
// Exactly which rappy is constructed depends on param6 (or the random
// rare check) and the current season event:
// Ep1/Ep2 non-rare = Rag Rappy
// Ep4 non-rare = Sand Rappy (Crater or Desert variation)
// Ep1 rare = Al Rappy
// Ep2 rare, Christmas = Saint Rappy
// Ep2 rare, Easter = Egg Rappy
// Ep2 rare, Halloween = Hallo Rappy
// Ep2 rare, any other season event (or none) = Love Rappy
// Ep4 rare = Del Rappy (Crater or Desert variation)
{0x0041, "TObjEneLappy"}, {0x0041, "TObjEneLappy"},
// Monest (and Mothmants). Params:
// param2 = number of Mothmants to expel at start (clamped to [0, 6])
// param3 = total Mothmants (clamped to [0, min(30, num_children)]
// where num_children comes from the EnemySetEntry; if this is less
// than param2, then param2 will take precedence but no further
// Mothmants will emerge after the first group)
// Note: In map_forest01_02e.dat in the vanilla map files there is a
// Monest that has param1 = 3 and param2 = 10. This looks like just an
// off-by-one error on Sega's part where they accidentally shifted the
// parameters down by one place. As described above, this Monest expels
// 6 Mothmants, then no more after they are killed.
{0x0042, "TObjEneBm3FlyNest"}, {0x0042, "TObjEneBm3FlyNest"},
// Savage Wolf or Barbarous Wolf. Params:
// param1 = group number (when a Barbarous Wolf dies, all wolves with
// the same group number howl and trigger their buffs or weaknesses)
// param2 = if less than 1, this is a Savage Wolf; otherwise it's a
// Barbarous Wolf
{0x0043, "TObjEneBm5Wolf"}, {0x0043, "TObjEneBm5Wolf"},
// Booma, Gobooma, or Gigobooma. Params:
// param1 = TODO (see TObjEneBeast_v5A)
// param2 = idle walk radius (when there's no target, it will walk
// around its spawn location within this radius; if this is zero, it
// stands still instead)
// param6 = type (0 = Booma, 1 = Gobooma, 2 = Gigobooma)
// param7 = TODO (see TObjEnemy_FUN_800f6f3c)
{0x0044, "TObjEneBeast"}, {0x0044, "TObjEneBeast"},
{0x0045, "TObjNpcLappy"},
{0x0046, "TObjNpcMoja"}, // Grass Assassin. Params:
{0x0047, "TObjNpcRico"}, // v2 only (not v1 nor v3+) // param1 = TODO
// param2 = TODO (some state is set based on whather this is <= 0 or
// not, but the value is also used directly in some places)
// param3 = TODO (see TObjGrass_update_case8)
// param4 = TODO (see TObjGrass_update_case8)
// It seems there was support for multiple models at one point via
// param6, but the final game overwrites param6 with 0 before selecting
// the model.
{0x0060, "TObjGrass"}, {0x0060, "TObjGrass"},
{0x0061, "TObjEneRe2Flower"},
{0x0062, "TObjEneNanoDrago"}, // TODO: Describe the rest of the enemy types.
{0x0063, "TObjEneShark"}, {0x0061, "TObjEneRe2Flower"}, // Constructor in 3OE1: 800C42E0
{0x0064, "TObjEneSlime"}, {0x0062, "TObjEneNanoDrago"}, // Constructor in 3OE1: 800DBDF0
{0x0065, "TObjEnePanarms"}, {0x0063, "TObjEneShark"}, // Constructor in 3OE1: 800AC028
{0x0080, "TObjEneDubchik"}, {0x0064, "TObjEneSlime"}, // Constructor in 3OE1: 800EBC2C
{0x0081, "TObjEneGyaranzo"}, {0x0065, "TObjEnePanarms"}, // Constructor in 3OE1: 800DF548
{0x0082, "TObjEneMe3ShinowaReal"}, {0x0080, "TObjEneDubchik"}, // Constructor in 3OE1: 800AA4E4
{0x0083, "TObjEneMe1Canadin"}, {0x0081, "TObjEneGyaranzo"}, // Constructor in 3OE1: 800D39A0
{0x0084, "TObjEneMe1CanadinLeader"}, {0x0082, "TObjEneMe3ShinowaReal"}, // Constructor in 3OE1: 800E79DC
{0x0085, "TOCtrlDubchik"}, {0x0083, "TObjEneMe1Canadin"}, // Constructor in 3OE1: 8009F360
{0x00A0, "TObjEneSaver"}, {0x0084, "TObjEneMe1CanadinLeader"}, // Constructor in 3OE1: 8009B6C8
{0x00A1, "TObjEneRe4Sorcerer"}, {0x0085, "TOCtrlDubchik"}, // Constructor in 3OE1: 8015D170
{0x00A2, "TObjEneDarkGunner"}, {0x00A0, "TObjEneSaver"}, // Constructor in 3OE1: 800A6E98
{0x00A3, "TObjEneDarkGunCenter"}, {0x00A1, "TObjEneRe4Sorcerer"}, // Constructor in 3OE1: 800F0280
{0x00A4, "TObjEneDf2Bringer"}, {0x00A2, "TObjEneDarkGunner"}, // Constructor in 3OE1: 800A2B70
{0x00A5, "TObjEneRe7Berura"}, {0x00A3, "TObjEneDarkGunCenter"}, // Constructor in 3OE1: 800A0C70
{0x00A6, "TObjEneDimedian"}, {0x00A4, "TObjEneDf2Bringer"}, // Constructor in 3OE1: 800999E4
{0x00A7, "TObjEneBalClawBody"}, {0x00A5, "TObjEneRe7Berura"}, // Constructor in 3OE1: 80095814
{0x00A8, "__TObjEneBalClawClaw_SUBCLASS__"}, {0x00A6, "TObjEneDimedian"}, // Constructor in 3OE1: 800A7E28
{0x00A9, "TObjNpcBringer"}, {0x00A7, "TObjEneBalClawBody"}, // Constructor in 3OE1: 8008FF78
{0x00C0, "TBoss1Dragon/TBoss5Gryphon"}, {0x00A8, "__TObjEneBalClawClaw_SUBCLASS__"}, // Constructor in 3OE1: 800917D8
{0x00C1, "TBoss2DeRolLe"}, {0x00C0, "TBoss1Dragon/TBoss5Gryphon"}, // Constructor in 3OE1: 8002A434
{0x00C2, "TBoss3Volopt"}, {0x00C1, "TBoss2DeRolLe"}, // Constructor in 3OE1: 80035D10
{0x00C3, "TBoss3VoloptP01"}, {0x00C2, "TBoss3Volopt"}, // Constructor in 3OE1: 8003EDB0
{0x00C4, "TBoss3VoloptCore/SUBCLASS"}, {0x00C3, "TBoss3VoloptP01"}, // Constructor in 3OE1: 80043FC4
{0x00C5, "__TObjEnemyCustom_SUBCLASS__"}, {0x00C4, "TBoss3VoloptCore/SUBCLASS"}, // Constructor in 3OE1: 80040818
{0x00C6, "TBoss3VoloptMonitor"}, {0x00C5, "__TObjEnemyCustom_SUBCLASS__"}, // Constructor in 3OE1: 80047E0C
{0x00C7, "TBoss3VoloptHiraisin"}, {0x00C6, "TBoss3VoloptMonitor"}, // Constructor in 3OE1: 800424FC
{0x00C8, "TBoss4DarkFalz"}, {0x00C7, "TBoss3VoloptHiraisin"}, // Constructor in 3OE1: 80041854
{0x00CA, "TBoss6PlotFalz"}, // v3+ only {0x00C8, "TBoss4DarkFalz"}, // Constructor in 3OE1: 8004C16C
{0x00CB, "TBoss7DeRolLeC"}, // v3+ only {0x00CA, "TBoss6PlotFalz"}, // Constructor in 3OE1: 802AB714 // v3+ only
{0x00CC, "TBoss8Dragon"}, // v3+ only {0x00CB, "TBoss7DeRolLeC"}, // Constructor in 3OE1: 802ECB38 // v3+ only
{0x00D0, "TObjNpcKenkyu"}, // v3+ only {0x00CC, "TBoss8Dragon"}, // Constructor in 3OE1: 802FC03C // v3+ only
{0x00D1, "TObjNpcSoutokufu"}, // v3+ only {0x00D4, "TObjEneMe3StelthReal/TObjNpcHeroScientist"}, // Constructor in 3OE1: 800F5230 // Ep3/v3+ only
{0x00D2, "TObjNpcHosa"}, // v3+ only {0x00D5, "TObjEneMerillLia/TObjNpcHeroScientist"}, // Constructor in 3OE1: 800D6ACC // Ep3/v3+ only
{0x00D3, "TObjNpcKenkyuW"}, // v3+ only {0x00D6, "TObjEneBm9Mericarol/TObjNpcHeroGovernor"}, // Constructor in 3OE1: 802CFABC // Ep3/v3+ only
{0x00D4, "TObjEneMe3StelthReal/TObjNpcHeroScientist"}, // Ep3/v3+ only {0x00D7, "TObjEneBm5GibonU/TObjNpcHeroGovernor"}, // Constructor in 3OE1: 800D17AC // Ep3/v3+ only
{0x00D5, "TObjEneMerillLia/TObjNpcHeroScientist"}, // Ep3/v3+ only {0x00D8, "TObjEneGibbles"}, // Constructor in 3OE1: 802DA0E0 // v3+ only
{0x00D6, "TObjEneBm9Mericarol/TObjNpcHeroGovernor"}, // Ep3/v3+ only {0x00D9, "TObjEneMe1Gee"}, // Constructor in 3OE1: 800CC768 // v3+ only
{0x00D7, "TObjEneBm5GibonU/TObjNpcHeroGovernor"}, // Ep3/v3+ only {0x00DA, "TObjEneMe1GiGue"}, // Constructor in 3OE1: 802CBF30 // v3+ only
{0x00D8, "TObjEneGibbles"}, // v3+ only {0x00DB, "TObjEneDelDepth"}, // Constructor in 3OE1: 803141F0 // v3+ only
{0x00D9, "TObjEneMe1Gee"}, // v3+ only {0x00DC, "TObjEneDellBiter"}, // Constructor in 3OE1: 80304E1C // v3+ only
{0x00DA, "TObjEneMe1GiGue"}, // v3+ only {0x00DD, "TObjEneDolmOlm"}, // Constructor in 3OE1: 80300C5C // v3+ only
{0x00DB, "TObjEneDelDepth"}, // v3+ only {0x00DE, "TObjEneMorfos"}, // Constructor in 3OE1: 80333584 // v3+ only
{0x00DC, "TObjEneDellBiter"}, // v3+ only {0x00DF, "TObjEneRecobox"}, // Constructor in 3OE1: 8031E7A0 // v3+ only
{0x00DD, "TObjEneDolmOlm"}, // v3+ only {0x00E0, "TObjEneMe3SinowZoaReal/TObjEneEpsilonBody"}, // Constructor in 3OE1: 803197AC // v3+ only
{0x00DE, "TObjEneMorfos"}, // v3+ only {0x00E1, "TObjEneIllGill"}, // Constructor in 3OE1: 8036685C // v3+ only
{0x00DF, "TObjEneRecobox"}, // v3+ only {0x0100, "__UNKNOWN_NPC_0100__"}, // Constructor in 59NL: 0060E128 // v4 only
{0x00E0, "TObjEneMe3SinowZoaReal/TObjEneEpsilonBody"}, // v3+ only {0x0110, "__ASTARK__/TObjNpcWalkingMeka_Hero"}, // Constructor in 59NL: 005A3D60; 3SE0: 80271DB0 // Ep3/v4 only
{0x00E1, "TObjEneIllGill"}, // v3+ only {0x0111, "__YOWIE__/__SATELLITE_LIZARD__/TObjNpcWalkingMeka_Dark"}, // Constructor in 59NL: 005AE7CC; 3SE0: 80271790 // Ep3/v4 only
{0x00F0, "TObjNpcHosa2"}, // v3+ only {0x0112, "__MERISSA_A__/TObjNpcHeroAide"}, // Constructor in 59NL: 005B6B24; 3SE0: 802F4888 // Ep3/v4 only
{0x00F1, "TObjNpcKenkyu2"}, // v3+ only {0x0113, "__GIRTABLULU__"}, // Constructor in 59NL: 005AB9AC // v4 only
{0x00F2, "TObjNpcNgcBase"}, // v3+ only {0x0114, "__ZU__"}, // Constructor in 59NL: 005B47B8 // v4 only
{0x00F3, "TObjNpcNgcBase"}, // v3+ only {0x0115, "__BOOTA_FAMILY__"}, // Constructor in 59NL: 005A5C08 // v4 only
{0x00F4, "TObjNpcNgcBase"}, // v3+ only {0x0116, "__DORPHON__"}, // Constructor in 59NL: 005A673C // v4 only
{0x00F5, "TObjNpcNgcBase"}, // v3+ only {0x0117, "__GORAN_FAMILY__"}, // Constructor in 59NL: 005ADAC4 // v4 only
{0x00F6, "TObjNpcNgcBase"}, // v3+ only {0x0118, "__UNKNOWN_0118__"}, // Constructor in 59NL: 00602A14 // v4 only
{0x00F7, "TObjNpcNgcBase"}, // v3+ only {0x0119, "__EPISODE_4_BOSS__"}, // Constructor in 59NL: 0076A86C // v4 only
{0x00F8, "TObjNpcNgcBase"}, // v3+ only
{0x00F9, "TObjNpcNgcBase"}, // v3+ only
{0x00FA, "TObjNpcNgcBase"}, // v3+ only
{0x00FB, "TObjNpcNgcBase"}, // v3+ only
{0x00FC, "TObjNpcNgcBase"}, // v3+ only
{0x00FD, "TObjNpcNgcBase"}, // v3+ only
{0x00FE, "TObjNpcNgcBase"}, // v3+ only
{0x00FF, "TObjNpcNgcBase"}, // v3+ only
{0x0100, "__UNKNOWN_NPC_0100__"}, // v4 only
{0x0110, "__ASTARK__/TObjNpcWalkingMeka_Hero"}, // Ep3/v4 only
{0x0111, "__YOWIE__/__SATELLITE_LIZARD__/TObjNpcWalkingMeka_Dark"}, // Ep3/v4 only
{0x0112, "__MERISSA_A__/TObjNpcHeroAide"}, // Ep3/v4 only
{0x0113, "__GIRTABLULU__"}, // v4 only
{0x0114, "__ZU__"}, // v4 only
{0x0115, "__BOOTA_FAMILY__"}, // v4 only
{0x0116, "__DORPHON__"}, // v4 only
{0x0117, "__GORAN_FAMILY__"}, // v4 only
{0x0118, "__UNKNOWN_0118__"}, // v4 only
{0x0119, "__EPISODE_4_BOSS__"}, // v4 only
}); });
try { try {
return names.at(type); return names.at(type);
@@ -2758,12 +2873,12 @@ string MapFile::ObjectSetEntry::str() const {
this->angle.x.load(), this->angle.x.load(),
this->angle.y.load(), this->angle.y.load(),
this->angle.z.load(), this->angle.z.load(),
this->fparam1.load(), this->param1.load(),
this->fparam2.load(), this->param2.load(),
this->fparam3.load(), this->param3.load(),
this->iparam4.load(), this->param4.load(),
this->iparam5.load(), this->param5.load(),
this->iparam6.load(), this->param6.load(),
this->unused.load()); this->unused.load());
} }
@@ -2773,12 +2888,12 @@ uint64_t MapFile::ObjectSetEntry::semantic_hash(uint8_t floor) const {
ret = phosg::fnv1a64(&this->room, sizeof(this->room), ret); ret = phosg::fnv1a64(&this->room, sizeof(this->room), ret);
ret = phosg::fnv1a64(&this->pos, sizeof(this->pos), ret); ret = phosg::fnv1a64(&this->pos, sizeof(this->pos), ret);
ret = phosg::fnv1a64(&this->angle, sizeof(this->angle), ret); ret = phosg::fnv1a64(&this->angle, sizeof(this->angle), ret);
ret = phosg::fnv1a64(&this->fparam1, sizeof(this->fparam1), ret); ret = phosg::fnv1a64(&this->param1, sizeof(this->param1), ret);
ret = phosg::fnv1a64(&this->fparam2, sizeof(this->fparam2), ret); ret = phosg::fnv1a64(&this->param2, sizeof(this->param2), ret);
ret = phosg::fnv1a64(&this->fparam3, sizeof(this->fparam3), ret); ret = phosg::fnv1a64(&this->param3, sizeof(this->param3), ret);
ret = phosg::fnv1a64(&this->iparam4, sizeof(this->iparam4), ret); ret = phosg::fnv1a64(&this->param4, sizeof(this->param4), ret);
ret = phosg::fnv1a64(&this->iparam5, sizeof(this->iparam5), ret); ret = phosg::fnv1a64(&this->param5, sizeof(this->param5), ret);
ret = phosg::fnv1a64(&this->iparam6, sizeof(this->iparam6), ret); ret = phosg::fnv1a64(&this->param6, sizeof(this->param6), ret);
ret = phosg::fnv1a64(&floor, sizeof(floor), ret); ret = phosg::fnv1a64(&floor, sizeof(floor), ret);
return ret; return ret;
} }
@@ -2802,13 +2917,13 @@ string MapFile::EnemySetEntry::str() const {
this->angle.x.load(), this->angle.x.load(),
this->angle.y.load(), this->angle.y.load(),
this->angle.z.load(), this->angle.z.load(),
this->fparam1.load(), this->param1.load(),
this->fparam2.load(), this->param2.load(),
this->fparam3.load(), this->param3.load(),
this->fparam4.load(), this->param4.load(),
this->fparam5.load(), this->param5.load(),
this->iparam6.load(), this->param6.load(),
this->iparam7.load(), this->param7.load(),
this->unused.load()); this->unused.load());
} }
@@ -2820,13 +2935,13 @@ uint64_t MapFile::EnemySetEntry::semantic_hash(uint8_t floor) const {
ret = phosg::fnv1a64(&this->wave_number2, sizeof(this->wave_number2), ret); ret = phosg::fnv1a64(&this->wave_number2, sizeof(this->wave_number2), ret);
ret = phosg::fnv1a64(&this->pos, sizeof(this->pos), ret); ret = phosg::fnv1a64(&this->pos, sizeof(this->pos), ret);
ret = phosg::fnv1a64(&this->angle, sizeof(this->angle), ret); ret = phosg::fnv1a64(&this->angle, sizeof(this->angle), ret);
ret = phosg::fnv1a64(&this->fparam1, sizeof(this->fparam1), ret); ret = phosg::fnv1a64(&this->param1, sizeof(this->param1), ret);
ret = phosg::fnv1a64(&this->fparam2, sizeof(this->fparam2), ret); ret = phosg::fnv1a64(&this->param2, sizeof(this->param2), ret);
ret = phosg::fnv1a64(&this->fparam3, sizeof(this->fparam3), ret); ret = phosg::fnv1a64(&this->param3, sizeof(this->param3), ret);
ret = phosg::fnv1a64(&this->fparam4, sizeof(this->fparam4), ret); ret = phosg::fnv1a64(&this->param4, sizeof(this->param4), ret);
ret = phosg::fnv1a64(&this->fparam5, sizeof(this->fparam5), ret); ret = phosg::fnv1a64(&this->param5, sizeof(this->param5), ret);
ret = phosg::fnv1a64(&this->iparam6, sizeof(this->iparam6), ret); ret = phosg::fnv1a64(&this->param6, sizeof(this->param6), ret);
ret = phosg::fnv1a64(&this->iparam7, sizeof(this->iparam7), ret); ret = phosg::fnv1a64(&this->param7, sizeof(this->param7), ret);
ret = phosg::fnv1a64(&floor, sizeof(floor), ret); ret = phosg::fnv1a64(&floor, sizeof(floor), ret);
return ret; return ret;
} }
@@ -2879,13 +2994,13 @@ string MapFile::RandomEnemyLocationEntry::str() const {
string MapFile::RandomEnemyDefinition::str() const { string MapFile::RandomEnemyDefinition::str() const {
return phosg::string_printf("[RandomEnemyDefinition params=[%g %g %g %g %g %04hX %04hX] entry_num=%08" PRIX32 " min_children=%04hX max_children=%04hX]", return phosg::string_printf("[RandomEnemyDefinition params=[%g %g %g %g %g %04hX %04hX] entry_num=%08" PRIX32 " min_children=%04hX max_children=%04hX]",
this->fparam1.load(), this->param1.load(),
this->fparam2.load(), this->param2.load(),
this->fparam3.load(), this->param3.load(),
this->fparam4.load(), this->param4.load(),
this->fparam5.load(), this->param5.load(),
this->iparam6.load(), this->param6.load(),
this->iparam7.load(), this->param7.load(),
this->entry_num.load(), this->entry_num.load(),
this->min_children.load(), this->min_children.load(),
this->max_children.load()); this->max_children.load());
@@ -3243,13 +3358,13 @@ std::shared_ptr<MapFile> MapFile::materialize_random_sections(uint32_t random_se
const auto& def = definitions_r.pget<RandomEnemyDefinition>(bs_min * sizeof(RandomEnemyDefinition)); const auto& def = definitions_r.pget<RandomEnemyDefinition>(bs_min * sizeof(RandomEnemyDefinition));
if (def.entry_num == weight_entry.def_entry_num) { if (def.entry_num == weight_entry.def_entry_num) {
e.fparam1 = def.fparam1; e.param1 = def.param1;
e.fparam2 = def.fparam2; e.param2 = def.param2;
e.fparam3 = def.fparam3; e.param3 = def.param3;
e.fparam4 = def.fparam4; e.param4 = def.param4;
e.fparam5 = def.fparam5; e.param5 = def.param5;
e.iparam6 = def.iparam6; e.param6 = def.param6;
e.iparam7 = def.iparam7; e.param7 = def.param7;
e.num_children = random_state.rand_int_biased(def.min_children, def.max_children); e.num_children = random_state.rand_int_biased(def.min_children, def.max_children);
} else { } else {
throw runtime_error("random enemy definition not found"); throw runtime_error("random enemy definition not found");
@@ -3681,7 +3796,7 @@ void SuperMap::link_object_version(std::shared_ptr<Object> obj, Version version,
entities.object_for_floor_room_and_group.emplace(k, obj); entities.object_for_floor_room_and_group.emplace(k, obj);
// Add to door index // Add to door index
uint32_t base_switch_flag = set_entry->iparam4; uint32_t base_switch_flag = set_entry->param4;
uint32_t num_switch_flags = 0; uint32_t num_switch_flags = 0;
switch (set_entry->base_type) { switch (set_entry->base_type) {
case 0x01AB: // TODoorFourLightRuins case 0x01AB: // TODoorFourLightRuins
@@ -3689,11 +3804,11 @@ void SuperMap::link_object_version(std::shared_ptr<Object> obj, Version version,
case 0x0202: // TObjDoorJung case 0x0202: // TObjDoorJung
case 0x0221: // TODoorFourLightSeabed case 0x0221: // TODoorFourLightSeabed
case 0x0222: // TODoorFourLightSeabedU case 0x0222: // TODoorFourLightSeabedU
num_switch_flags = set_entry->iparam5; num_switch_flags = set_entry->param5;
break; break;
case 0x00C1: // TODoorCave01 case 0x00C1: // TODoorCave01
case 0x0100: // TODoorMachine01 case 0x0100: // TODoorMachine01
num_switch_flags = (4 - clamp<size_t>(set_entry->iparam5, 0, 4)); num_switch_flags = (4 - clamp<size_t>(set_entry->param5, 0, 4));
break; break;
case 0x014A: // TODoorAncient08 case 0x014A: // TODoorAncient08
num_switch_flags = 4; num_switch_flags = 4;
@@ -3830,13 +3945,13 @@ shared_ptr<SuperMap::Enemy> SuperMap::add_enemy_and_children(
add(EnemyType::NON_ENEMY_NPC); add(EnemyType::NON_ENEMY_NPC);
break; break;
case 0x0040: { // TObjEneMoja case 0x0040: { // TObjEneMoja
bool is_rare = (set_entry->iparam6.load() >= 1); bool is_rare = (set_entry->param6.load() >= 1);
add(EnemyType::HILDEBEAR, is_rare, is_rare); add(EnemyType::HILDEBEAR, is_rare, is_rare);
break; break;
} }
case 0x0041: { // TObjEneLappy case 0x0041: { // TObjEneLappy
bool is_rare_v123 = (set_entry->iparam6 != 0); bool is_rare_v123 = (set_entry->param6 != 0);
bool is_rare_bb = (set_entry->iparam6 & 1); bool is_rare_bb = (set_entry->param6 & 1);
switch (this->episode) { switch (this->episode) {
case Episode::EP1: case Episode::EP1:
case Episode::EP2: case Episode::EP2:
@@ -3856,11 +3971,11 @@ shared_ptr<SuperMap::Enemy> SuperMap::add_enemy_and_children(
default_num_children = 30; default_num_children = 30;
break; break;
case 0x0043: // TObjEneBm5Wolf case 0x0043: // TObjEneBm5Wolf
add((set_entry->fparam2 >= 1) ? EnemyType::BARBAROUS_WOLF : EnemyType::SAVAGE_WOLF); add((set_entry->param2 >= 1) ? EnemyType::BARBAROUS_WOLF : EnemyType::SAVAGE_WOLF);
break; break;
case 0x0044: { // TObjEneBeast case 0x0044: { // TObjEneBeast
static const EnemyType types[3] = {EnemyType::BOOMA, EnemyType::GOBOOMA, EnemyType::GIGOBOOMA}; static const EnemyType types[3] = {EnemyType::BOOMA, EnemyType::GOBOOMA, EnemyType::GIGOBOOMA};
add(types[clamp<int16_t>(set_entry->iparam6, 0, 2)]); add(types[clamp<int16_t>(set_entry->param6, 0, 2)]);
break; break;
} }
case 0x0060: // TObjGrass case 0x0060: // TObjGrass
@@ -3874,13 +3989,13 @@ shared_ptr<SuperMap::Enemy> SuperMap::add_enemy_and_children(
break; break;
case 0x0063: { // TObjEneShark case 0x0063: { // TObjEneShark
static const EnemyType types[3] = {EnemyType::EVIL_SHARK, EnemyType::PAL_SHARK, EnemyType::GUIL_SHARK}; static const EnemyType types[3] = {EnemyType::EVIL_SHARK, EnemyType::PAL_SHARK, EnemyType::GUIL_SHARK};
add(types[clamp<int16_t>(set_entry->iparam6, 0, 2)]); add(types[clamp<int16_t>(set_entry->param6, 0, 2)]);
break; break;
} }
case 0x0064: { // TObjEneSlime case 0x0064: { // TObjEneSlime
// Unlike all other versions, BB doesn't have a way to force slimes to be // Unlike all other versions, BB doesn't have a way to force slimes to be
// rare via constructor args // rare via constructor args
bool is_rare_v123 = (set_entry->iparam7 & 1); bool is_rare_v123 = (set_entry->param7 & 1);
default_num_children = -1; // Skip adding children later (because we do it here) default_num_children = -1; // Skip adding children later (because we do it here)
size_t num_children = set_entry->num_children ? set_entry->num_children.load() : 4; size_t num_children = set_entry->num_children ? set_entry->num_children.load() : 4;
for (size_t z = 0; z < num_children + 1; z++) { for (size_t z = 0; z < num_children + 1; z++) {
@@ -3898,13 +4013,13 @@ shared_ptr<SuperMap::Enemy> SuperMap::add_enemy_and_children(
add(EnemyType::MIGIUM); add(EnemyType::MIGIUM);
break; break;
case 0x0080: // TObjEneDubchik case 0x0080: // TObjEneDubchik
add((set_entry->iparam6 != 0) ? EnemyType::GILLCHIC : EnemyType::DUBCHIC); add((set_entry->param6 != 0) ? EnemyType::GILLCHIC : EnemyType::DUBCHIC);
break; break;
case 0x0081: // TObjEneGyaranzo case 0x0081: // TObjEneGyaranzo
add(EnemyType::GARANZ); add(EnemyType::GARANZ);
break; break;
case 0x0082: // TObjEneMe3ShinowaReal case 0x0082: // TObjEneMe3ShinowaReal
add((set_entry->fparam2 >= 1) ? EnemyType::SINOW_GOLD : EnemyType::SINOW_BEAT); add((set_entry->param2 >= 1) ? EnemyType::SINOW_GOLD : EnemyType::SINOW_BEAT);
default_num_children = 4; default_num_children = 4;
break; break;
case 0x0083: // TObjEneMe1Canadin case 0x0083: // TObjEneMe1Canadin
@@ -3944,7 +4059,7 @@ shared_ptr<SuperMap::Enemy> SuperMap::add_enemy_and_children(
break; break;
case 0x00A6: { // TObjEneDimedian case 0x00A6: { // TObjEneDimedian
static const EnemyType types[3] = {EnemyType::DIMENIAN, EnemyType::LA_DIMENIAN, EnemyType::SO_DIMENIAN}; static const EnemyType types[3] = {EnemyType::DIMENIAN, EnemyType::LA_DIMENIAN, EnemyType::SO_DIMENIAN};
add(types[clamp<int16_t>(set_entry->iparam6, 0, 2)]); add(types[clamp<int16_t>(set_entry->param6, 0, 2)]);
break; break;
} }
case 0x00A7: // TObjEneBalClawBody case 0x00A7: // TObjEneBalClawBody
@@ -4029,14 +4144,14 @@ shared_ptr<SuperMap::Enemy> SuperMap::add_enemy_and_children(
default_num_children = 5; default_num_children = 5;
break; break;
case 0x00D4: // TObjEneMe3StelthReal case 0x00D4: // TObjEneMe3StelthReal
add((set_entry->iparam6 > 0) ? EnemyType::SINOW_SPIGELL : EnemyType::SINOW_BERILL); add((set_entry->param6 > 0) ? EnemyType::SINOW_SPIGELL : EnemyType::SINOW_BERILL);
default_num_children = 4; default_num_children = 4;
break; break;
case 0x00D5: // TObjEneMerillLia case 0x00D5: // TObjEneMerillLia
add((set_entry->iparam6 > 0) ? EnemyType::MERILTAS : EnemyType::MERILLIA); add((set_entry->param6 > 0) ? EnemyType::MERILTAS : EnemyType::MERILLIA);
break; break;
case 0x00D6: { // TObjEneBm9Mericarol case 0x00D6: { // TObjEneBm9Mericarol
switch (set_entry->iparam6) { switch (set_entry->param6) {
case 0: case 0:
add(EnemyType::MERICAROL); add(EnemyType::MERICAROL);
break; break;
@@ -4052,7 +4167,7 @@ shared_ptr<SuperMap::Enemy> SuperMap::add_enemy_and_children(
break; break;
} }
case 0x00D7: // TObjEneBm5GibonU case 0x00D7: // TObjEneBm5GibonU
add((set_entry->iparam6 > 0) ? EnemyType::ZOL_GIBBON : EnemyType::UL_GIBBON); add((set_entry->param6 > 0) ? EnemyType::ZOL_GIBBON : EnemyType::UL_GIBBON);
break; break;
case 0x00D8: // TObjEneGibbles case 0x00D8: // TObjEneGibbles
add(EnemyType::GIBBLES); add(EnemyType::GIBBLES);
@@ -4070,7 +4185,7 @@ shared_ptr<SuperMap::Enemy> SuperMap::add_enemy_and_children(
add(EnemyType::DELBITER); add(EnemyType::DELBITER);
break; break;
case 0x00DD: // TObjEneDolmOlm case 0x00DD: // TObjEneDolmOlm
add((set_entry->iparam6 > 0) ? EnemyType::DOLMDARL : EnemyType::DOLMOLM); add((set_entry->param6 > 0) ? EnemyType::DOLMDARL : EnemyType::DOLMOLM);
break; break;
case 0x00DE: // TObjEneMorfos case 0x00DE: // TObjEneMorfos
add(EnemyType::MORFOS); add(EnemyType::MORFOS);
@@ -4085,7 +4200,7 @@ shared_ptr<SuperMap::Enemy> SuperMap::add_enemy_and_children(
default_num_children = 4; default_num_children = 4;
child_type = EnemyType::EPSIGARD; child_type = EnemyType::EPSIGARD;
} else { } else {
add((set_entry->iparam6 > 0) ? EnemyType::SINOW_ZELE : EnemyType::SINOW_ZOA); add((set_entry->param6 > 0) ? EnemyType::SINOW_ZELE : EnemyType::SINOW_ZOA);
} }
break; break;
case 0x00E1: // TObjEneIllGill case 0x00E1: // TObjEneIllGill
@@ -4096,13 +4211,13 @@ shared_ptr<SuperMap::Enemy> SuperMap::add_enemy_and_children(
break; break;
case 0x0111: case 0x0111:
if (floor > 0x05) { if (floor > 0x05) {
add(set_entry->fparam2 ? EnemyType::YOWIE_DESERT : EnemyType::SATELLITE_LIZARD_DESERT); add(set_entry->param2 ? EnemyType::YOWIE_DESERT : EnemyType::SATELLITE_LIZARD_DESERT);
} else { } else {
add(set_entry->fparam2 ? EnemyType::YOWIE_CRATER : EnemyType::SATELLITE_LIZARD_CRATER); add(set_entry->param2 ? EnemyType::YOWIE_CRATER : EnemyType::SATELLITE_LIZARD_CRATER);
} }
break; break;
case 0x0112: { case 0x0112: {
bool is_rare = (set_entry->iparam6 & 1); bool is_rare = (set_entry->param6 & 1);
add(EnemyType::MERISSA_A, is_rare, is_rare); add(EnemyType::MERISSA_A, is_rare, is_rare);
break; break;
} }
@@ -4110,29 +4225,29 @@ shared_ptr<SuperMap::Enemy> SuperMap::add_enemy_and_children(
add(EnemyType::GIRTABLULU); add(EnemyType::GIRTABLULU);
break; break;
case 0x0114: { case 0x0114: {
bool is_rare = (set_entry->iparam6 & 1); bool is_rare = (set_entry->param6 & 1);
add((floor > 0x05) ? EnemyType::ZU_DESERT : EnemyType::ZU_CRATER, is_rare, is_rare); add((floor > 0x05) ? EnemyType::ZU_DESERT : EnemyType::ZU_CRATER, is_rare, is_rare);
break; break;
} }
case 0x0115: { case 0x0115: {
static const EnemyType types[3] = {EnemyType::BOOTA, EnemyType::ZE_BOOTA, EnemyType::BA_BOOTA}; static const EnemyType types[3] = {EnemyType::BOOTA, EnemyType::ZE_BOOTA, EnemyType::BA_BOOTA};
add(types[clamp<int16_t>(set_entry->iparam6, 0, 2)]); add(types[clamp<int16_t>(set_entry->param6, 0, 2)]);
break; break;
} }
case 0x0116: { case 0x0116: {
bool is_rare = (set_entry->iparam6 & 1); bool is_rare = (set_entry->param6 & 1);
add(EnemyType::DORPHON, is_rare, is_rare); add(EnemyType::DORPHON, is_rare, is_rare);
break; break;
} }
case 0x0117: { case 0x0117: {
static const EnemyType types[3] = {EnemyType::GORAN, EnemyType::PYRO_GORAN, EnemyType::GORAN_DETONATOR}; static const EnemyType types[3] = {EnemyType::GORAN, EnemyType::PYRO_GORAN, EnemyType::GORAN_DETONATOR};
add(types[clamp<int16_t>(set_entry->iparam6, 0, 2)]); add(types[clamp<int16_t>(set_entry->param6, 0, 2)]);
break; break;
} }
case 0x0119: case 0x0119:
// There isn't a way to force the Episode 4 boss to be rare via // There isn't a way to force the Episode 4 boss to be rare via
// constructor args // constructor args
add((set_entry->iparam6 & 1) ? EnemyType::SHAMBERTIN : EnemyType::SAINT_MILION); add((set_entry->param6 & 1) ? EnemyType::SHAMBERTIN : EnemyType::SAINT_MILION);
default_num_children = 0x18; default_num_children = 0x18;
break; break;
@@ -4480,12 +4595,12 @@ static double object_set_edit_cost(const MapFile::ObjectSetEntry& prev, const Ma
((prev.group != current.group) * 50.0) + ((prev.group != current.group) * 50.0) +
((prev.room != current.room) * 50.0) + ((prev.room != current.room) * 50.0) +
(prev.pos - current.pos).norm() + (prev.pos - current.pos).norm() +
((prev.fparam1 != current.fparam1) * 10.0) + ((prev.param1 != current.param1) * 10.0) +
((prev.fparam2 != current.fparam2) * 10.0) + ((prev.param2 != current.param2) * 10.0) +
((prev.fparam3 != current.fparam3) * 10.0) + ((prev.param3 != current.param3) * 10.0) +
((prev.iparam4 != current.iparam4) * 10.0) + ((prev.param4 != current.param4) * 10.0) +
((prev.iparam5 != current.iparam5) * 10.0) + ((prev.param5 != current.param5) * 10.0) +
((prev.iparam6 != current.iparam6) * 10.0)); ((prev.param6 != current.param6) * 10.0));
} }
static double enemy_set_add_cost(const MapFile::EnemySetEntry&) { static double enemy_set_add_cost(const MapFile::EnemySetEntry&) {
@@ -4506,13 +4621,13 @@ static double enemy_set_edit_cost(const MapFile::EnemySetEntry& prev, const MapF
((prev.room != current.room) * 50.0) + ((prev.room != current.room) * 50.0) +
((prev.wave_number != current.wave_number) * 50.0) + ((prev.wave_number != current.wave_number) * 50.0) +
(prev.pos - current.pos).norm() + (prev.pos - current.pos).norm() +
((prev.fparam1 != current.fparam1) * 10.0) + ((prev.param1 != current.param1) * 10.0) +
((prev.fparam2 != current.fparam2) * 10.0) + ((prev.param2 != current.param2) * 10.0) +
((prev.fparam3 != current.fparam3) * 10.0) + ((prev.param3 != current.param3) * 10.0) +
((prev.fparam4 != current.fparam4) * 10.0) + ((prev.param4 != current.param4) * 10.0) +
((prev.fparam5 != current.fparam5) * 10.0) + ((prev.param5 != current.param5) * 10.0) +
((prev.iparam6 != current.iparam6) * 10.0) + ((prev.param6 != current.param6) * 10.0) +
((prev.iparam7 != current.iparam7) * 10.0)); ((prev.param7 != current.param7) * 10.0));
} }
static double event_add_cost(const MapFile::Event1Entry&) { static double event_add_cost(const MapFile::Event1Entry&) {
@@ -5469,7 +5584,7 @@ void MapState::index_super_map(const FloorConfig& fc, shared_ptr<PSOLFGEncryptio
} }
if (type == EnemyType::MERICARAND) { if (type == EnemyType::MERICARAND) {
// On v3, Mericarols that have iparam6 > 2 are randomized to be // On v3, Mericarols that have param6 > 2 are randomized to be
// Mericus, Merikle, or Mericarol, but the former two are not // Mericus, Merikle, or Mericarol, but the former two are not
// considered rare. (We use rare_flags anyway to distinguish them // considered rare. (We use rare_flags anyway to distinguish them
// from Mericarol.) // from Mericarol.)
+21 -21
View File
@@ -182,12 +182,12 @@ public:
// Angles are specified as 16-bit integers, where 0 is no rotation around // Angles are specified as 16-bit integers, where 0 is no rotation around
// the axis and FFFF is almost a complete counterclockwise rotation. // the axis and FFFF is almost a complete counterclockwise rotation.
/* 1C */ VectorXYZI angle; /* 1C */ VectorXYZI angle;
/* 28 */ le_float fparam1 = 0.0f; // Boxes: if <= 0, this is a specialized box, and the specialization is in param4/5/6 /* 28 */ le_float param1 = 0.0f; // Boxes: if <= 0, this is a specialized box, and the specialization is in param4/5/6
/* 2C */ le_float fparam2 = 0.0f; /* 2C */ le_float param2 = 0.0f;
/* 30 */ le_float fparam3 = 0.0f; // Boxes: if == 0, the item should be varied by difficulty and area /* 30 */ le_float param3 = 0.0f; // Boxes: if == 0, the item should be varied by difficulty and area
/* 34 */ le_int32_t iparam4 = 0; /* 34 */ le_int32_t param4 = 0;
/* 38 */ le_int32_t iparam5 = 0; /* 38 */ le_int32_t param5 = 0;
/* 3C */ le_int32_t iparam6 = 0; /* 3C */ le_int32_t param6 = 0;
/* 40 */ le_uint32_t unused = 0; // Reserved for pointer in client's memory; unused by server /* 40 */ le_uint32_t unused = 0; // Reserved for pointer in client's memory; unused by server
/* 44 */ /* 44 */
@@ -208,13 +208,13 @@ public:
/* 12 */ le_uint16_t unknown_a1 = 0; /* 12 */ le_uint16_t unknown_a1 = 0;
/* 14 */ VectorXYZF pos; /* 14 */ VectorXYZF pos;
/* 24 */ VectorXYZI angle; /* 24 */ VectorXYZI angle;
/* 2C */ le_float fparam1 = 0.0f; /* 2C */ le_float param1 = 0.0f;
/* 30 */ le_float fparam2 = 0.0f; /* 30 */ le_float param2 = 0.0f;
/* 34 */ le_float fparam3 = 0.0f; /* 34 */ le_float param3 = 0.0f;
/* 38 */ le_float fparam4 = 0.0f; /* 38 */ le_float param4 = 0.0f;
/* 3C */ le_float fparam5 = 0.0f; /* 3C */ le_float param5 = 0.0f;
/* 40 */ le_int16_t iparam6 = 0; /* 40 */ le_int16_t param6 = 0;
/* 42 */ le_int16_t iparam7 = 0; /* 42 */ le_int16_t param7 = 0;
/* 44 */ le_uint32_t unused = 0; // Reserved for pointer in client's memory; unused by server /* 44 */ le_uint32_t unused = 0; // Reserved for pointer in client's memory; unused by server
/* 48 */ /* 48 */
@@ -303,14 +303,14 @@ public:
struct RandomEnemyDefinition { // Section type 5 (RANDOM_ENEMY_DEFINITIONS) struct RandomEnemyDefinition { // Section type 5 (RANDOM_ENEMY_DEFINITIONS)
// All fields through entry_num map to the corresponding fields in // All fields through entry_num map to the corresponding fields in
// EnemySetEntry. Note that the order of the iparam fields is switched! // EnemySetEntry. Note that the order of param6 and param7 is switched!
/* 00 */ le_float fparam1; /* 00 */ le_float param1;
/* 04 */ le_float fparam2; /* 04 */ le_float param2;
/* 08 */ le_float fparam3; /* 08 */ le_float param3;
/* 0C */ le_float fparam4; /* 0C */ le_float param4;
/* 10 */ le_float fparam5; /* 10 */ le_float param5;
/* 14 */ le_int16_t iparam7; /* 14 */ le_int16_t param7;
/* 16 */ le_int16_t iparam6; /* 16 */ le_int16_t param6;
/* 18 */ le_uint32_t entry_num; /* 18 */ le_uint32_t entry_num;
/* 1C */ le_uint16_t min_children; /* 1C */ le_uint16_t min_children;
/* 1E */ le_uint16_t max_children; /* 1E */ le_uint16_t max_children;
+2 -2
View File
@@ -1042,8 +1042,8 @@ static HandlerResult SC_6x60_6xA2(shared_ptr<ProxyServer::LinkedSession> ses, co
res = ses->item_creator->on_box_item_drop(cmd.effective_area); res = ses->item_creator->on_box_item_drop(cmd.effective_area);
} else { } else {
ses->log.info("Creating item from box %04hX (area %02hX; specialized with %g %08" PRIX32 " %08" PRIX32 " %08" PRIX32 ")", ses->log.info("Creating item from box %04hX (area %02hX; specialized with %g %08" PRIX32 " %08" PRIX32 " %08" PRIX32 ")",
cmd.entity_index.load(), cmd.effective_area, cmd.fparam3.load(), cmd.iparam4.load(), cmd.iparam5.load(), cmd.iparam6.load()); cmd.entity_index.load(), cmd.effective_area, cmd.param3.load(), cmd.param4.load(), cmd.param5.load(), cmd.param6.load());
res = ses->item_creator->on_specialized_box_item_drop(cmd.effective_area, cmd.fparam3, cmd.iparam4, cmd.iparam5, cmd.iparam6); res = ses->item_creator->on_specialized_box_item_drop(cmd.effective_area, cmd.param3, cmd.param4, cmd.param5, cmd.param6);
} }
} else { } else {
ses->log.info("Creating item from enemy %04hX (area %02hX)", cmd.entity_index.load(), cmd.effective_area); ses->log.info("Creating item from enemy %04hX (area %02hX)", cmd.entity_index.load(), cmd.effective_area);
+8 -8
View File
@@ -2785,15 +2785,15 @@ DropReconcileResult reconcile_drop_request_with_map(
if (is_v1_or_v2(version) && (version != Version::GC_NTE)) { if (is_v1_or_v2(version) && (version != Version::GC_NTE)) {
// V1 and V2 don't have 6xA2, so we can't get ignore_def or the object // V1 and V2 don't have 6xA2, so we can't get ignore_def or the object
// parameters from the client on those versions // parameters from the client on those versions
cmd.fparam3 = set_entry->fparam3; cmd.param3 = set_entry->param3;
cmd.iparam4 = set_entry->iparam4; cmd.param4 = set_entry->param4;
cmd.iparam5 = set_entry->iparam5; cmd.param5 = set_entry->param5;
cmd.iparam6 = set_entry->iparam6; cmd.param6 = set_entry->param6;
} }
bool object_ignore_def = (set_entry->fparam1 > 0.0); bool object_ignore_def = (set_entry->param1 > 0.0);
if (res.ignore_def != object_ignore_def) { if (res.ignore_def != object_ignore_def) {
log.warning("ignore_def value %s from command does not match object\'s expected ignore_def %s (from p1=%g)", log.warning("ignore_def value %s from command does not match object\'s expected ignore_def %s (from p1=%g)",
res.ignore_def ? "true" : "false", object_ignore_def ? "true" : "false", set_entry->fparam1.load()); res.ignore_def ? "true" : "false", object_ignore_def ? "true" : "false", set_entry->param1.load());
} }
if (config.check_flag(Client::Flag::DEBUG_ENABLED)) { if (config.check_flag(Client::Flag::DEBUG_ENABLED)) {
send_text_message_printf(client_channel, "$C5K-%03zX %c %s", send_text_message_printf(client_channel, "$C5K-%03zX %c %s",
@@ -2907,9 +2907,9 @@ static void on_entity_drop_item_request(shared_ptr<Client> c, uint8_t command, u
} else { } else {
l->log.info("Creating item from box %04hX => K-%03zX (area %02hX; specialized with %g %08" PRIX32 " %08" PRIX32 " %08" PRIX32 ")", l->log.info("Creating item from box %04hX => K-%03zX (area %02hX; specialized with %g %08" PRIX32 " %08" PRIX32 " %08" PRIX32 ")",
cmd.entity_index.load(), rec.obj_st->k_id, cmd.effective_area, cmd.entity_index.load(), rec.obj_st->k_id, cmd.effective_area,
cmd.fparam3.load(), cmd.iparam4.load(), cmd.iparam5.load(), cmd.iparam6.load()); cmd.param3.load(), cmd.param4.load(), cmd.param5.load(), cmd.param6.load());
return l->item_creator->on_specialized_box_item_drop( return l->item_creator->on_specialized_box_item_drop(
cmd.effective_area, cmd.fparam3, cmd.iparam4, cmd.iparam5, cmd.iparam6); cmd.effective_area, cmd.param3, cmd.param4, cmd.param5, cmd.param6);
} }
} else if (rec.ene_st) { } else if (rec.ene_st) {
l->log.info("Creating item from enemy %04hX => E-%03zX (area %02hX)", l->log.info("Creating item from enemy %04hX => E-%03zX (area %02hX)",
+4 -4
View File
@@ -172,10 +172,10 @@ ShellCommand c_reload(
all - do all of the above\n\ all - do all of the above\n\
Reloading will not affect items that are in use; for example, if an Episode\n\ Reloading will not affect items that are in use; for example, if an Episode\n\
3 battle is in progress, it will continue to use the previous map and card\n\ 3 battle is in progress, it will continue to use the previous map and card\n\
definitions. Similarly, BB clients are not forced to disconnect or reload\n\ definitions until the battle ends. Similarly, BB clients are not forced to\n\
the battle parameters, so if these are changed without restarting, clients\n\ disconnect or reload the battle parameters, so if these are changed without\n\
may see (for example) EXP messages inconsistent with the amounts of EXP\n\ restarting, clients may see (for example) EXP messages inconsistent with\n\
actually received.", the amounts of EXP actually received.",
false, false,
+[](ShellCommand::Args& args) -> std::deque<std::string> { +[](ShellCommand::Args& args) -> std::deque<std::string> {
auto types = phosg::split(args.args, ' '); auto types = phosg::split(args.args, ' ');