Server:Boats

From The Al`Kabor Project Wiki
Jump to navigation Jump to search

For TAKP server, the boat routes use spawn conditions and are timed with grids. All synchronization has to be achieved via precise timing. Unfortunately, boats can't send send signals across zones. This is hard coded into the client.

If a grid is timed too short or too long, players will either get dropped off in the water or players will end up at the safe spot in the destination zone. Boat zones need to be static.

To skip straight to getting started, see #Boat_Route_Implementations

Server-Side Source[edit]

BoatID Constants[edit]

The following boats and their ID's are described as enum constants in the server source code: https://github.com/EQMacEmu/Server/blob/main/common/eq_constants.h#L685

//These are NPCIDs in the database. All of these boats send a BoardBoat opcode when boarded.
enum Boats
{
    Stormbreaker = 770, //freporte-oot-butcherblock
    SirensBane  = 771,
    Sea_King = 772, //erudext-erudsxing-qeynos
    Golden_Maiden  = 773,
    Maidens_Voyage  = 838, //timorous-firiona
    Bloated_Belly = 839, //timorous-overthere
    Barrel_Barge = 840, //Shuttle timorous-oasis
    Muckskimmer = 841,
    Sabrina = 24056, //Shuttle in Erud
    Island_Shuttle = 96075, //Shuttle to Elf docks in timorous
    Captains_Skiff = 842, //Shuttle timorous-butcherblock
    Icebreaker = 110083, //iceclad
    pirate_runners_skiff = 843 //Shuttle iceclad-nro
}; 

Boat ID's are stored in the player profile packet (pp.boatname and pp.boatid). When you board the boat, the boatid is registered in the PlayerProfile in the code (common/patches/mac_structs), sent by the client to the server,

struct PlayerProfile_Struct
{
 ...
 char    boat[32];
 ...
};

and temporarily stored in the `character_data`.boatid field in the database. When you depart a boat, the opposite is true.

Boarding and Leaving a boat have their own associated **OPCODE** and **EVENT** for purposes of scripting.


Opcodes[edit]

utils/patches/patch_Mac.conf:

OP_BoardBoat=0xbb41
OP_LeaveBoat=0xbc41
OP_ControlBoat=0x2641

These are the opcodes used by the TAKP client, but actual opcode values changed many times over the years. Several versions of these opcodes can be found in the TAKP source repository.

Boat Debugging[edit]

GM characters can observe player profile packets showing boats being boarded/departed by setting debug logging for Boats database, and they can also observe numerous other data about the boat's arrivals and departures, positions, grids, etc... via quest scripts associated with those Boats. To enable this information, see #Database_Settings below.

Database Settings[edit]

UPDATE logsys_categories SET log_to_gmsay = '3' WHERE log_category_description = 'Boats';

In order to see boat associated quest script debugging information, GM characters can see debug logging as well:

UPDATE logsys_categories SET log_to_gmsay = '3' WHERE log_category_description = 'Quest Debug';

Any values between 1 and 3 will enable logging (with increasing levels of detail), and 0 will disable it.

Source[edit]

The following snippets can be found at https://github.com/EQMacEmu/Server/blob/main/common/eqemu_logsys_log_aliases.h#L480

#define LogBoats(message, ...) do {\
    if (LogSys.log_settings[Logs::Boats].is_category_enabled == 1)\
        OutF(LogSys, Logs::General, Logs::Boats, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\
} while (0)

#define LogBoatsDetail(message, ...) do {\
    if (LogSys.log_settings[Logs::Boats].is_category_enabled == 1)\
        OutF(LogSys, Logs::Detail, Logs::Boats, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\
} while (0)

Common Ruletypes[edit]

common/ruletypes.h

RULE_INT ( Zone, BoatDistance, 50) //In zones where boat name is not set in the PP, this is how far away from the boat …
…r is the ocean floor, so if you have NPCs that float near the top (sharks, boats) disabling these rules may break them!
RULE_BOOL ( NPC, BoatsRunByDefault, true) // Mainly to make it easier to adjust boats' timing on the fly.

Zone[edit]

API[edit]

zone/zone.h and zone/zone.cpp:

bool Zone::IsBoatZone()

This function iterates through a hardcoded list of zone ids (qeynos, freporte, erudnext, butcher, oot, erudsxing, timorous, firiona, oasis, overthere, nro, iceclad) and returns true if the current zone id matches one of the zones in this list.


zone/npc.h and zone/npc.cpp:

bool NPC::IsBoat()
{
	return (GetBaseRace() == SHIP || GetBaseRace() == LAUNCH || GetBaseRace() == CONTROLLED_BOAT || GetBaseRace() == GHOST_SHIP);
}

From the context of NPC namespace, you can check if an NPC is a boat both in C++ server source and via LUA quest scripting.

zone/mob.h and zone/mob.cpp:

bool Mob::IsBoat() const 
{
	return (GetBaseRace() == SHIP || GetBaseRace() == LAUNCH || GetBaseRace() == CONTROLLED_BOAT || GetBaseRace() == GHOST_SHIP);
}

From the context of MOB namespace, you can check if an NPC is a boat both in C++ server source and via LUA quest scripting.

zone/client.h

The following functions are available for the server interacting with clients (found in the Client:: namespace).

uint16 GetBoatID() const { return BoatID; }
uint32 GetBoatNPCID() { return m_pp.boatid; }
char* GetBoatName() { return m_pp.boat; }
void SetBoatID(uint32 boatid);
void SetBoatName(const char* boatname);
void SendToBoat(bool messageonly) // Sometimes, the client doesn't send OP_LeaveBoat, so the boat values don't get cleared.
	                              // This can lead difficulty entering the zone, since some people's client's don't like
	                              // the boat timeout period.

These functions are also exposed via LUA quest scripting.

zone/client_packet.h

void Handle_OP_BoardBoat(const EQApplicationPacket *app);
void Handle_OP_ControlBoat(const EQApplicationPacket *app);
void Handle_OP_LeaveBoat(const EQApplicationPacket *app);

If you need to customize behavior when clients send packets indicating interaction with boats.

zone/command.h: These are GM commands ("#boatinfo" and "#resetboat")

void command_boatinfo(Client *c, const Seperator *sep);
void command_resetboat(Client *c, const Seperator *sep);

zone/entity.h:

void GetBoatInfo(Client* client);
uint8 GetClientCountByBoatNPCID(uint32 boatid);
uint8 GetClientCountByBoatID(uint32 entityid);

zone/lua_parser_events.h

void handle_board_boat(QuestInterface *parse, lua_State* L, Client* client, std::string data, uint32 extra_data,
		std::vector<std::any> *extra_pointers);
void handle_leave_boat(QuestInterface *parse, lua_State* L, Client* client, std::string data, uint32 extra_data,
		std::vector<std::any> *extra_pointers);

zone/lua_client.h:

int GetBoatID();
void SetBoatID(uint32 in_boatid);
char* GetBoatName();
void SetBoatName(const char* in_boatname);

zone/mob_movement_manager.h

void UpdatePathBoat(Mob *who, float x, float y, float z, MobMovementMode mode);

Zone Entry[edit]

Each time a client enters a zone, the server checks the client packet's player profile struct for a boatid > 0 and whether the client is currently in Timorous Deep or Firionia Vie. If this is true, the boatid in the player profile struct is set back to 0.

void Client::Handle_Connect_OP_ZoneEntry(const EQApplicationPacket *app)
{
    ...
    PlayerProfile_Struct* pps = (PlayerProfile_Struct*) new uchar[sizeof(PlayerProfile_Struct) - 4];
    memcpy(pps, &m_pp, sizeof(PlayerProfile_Struct) - 4);
    ...
	if(m_pp.boatid > 0 && (zone->GetZoneID() == timorous || zone->GetZoneID() == firiona))
		pps->boat[0] = 0;

So when the boat moves, we send the pc to their destination and the boatid. If the player finds that boatid in their destination zone, it will land on it. Otherwise, it will be sent to the zone safe location.

Boat Route Implementations[edit]

Implementing a boat is achieved via entries to the database and the LUA scripting engine. The original implementation on TAKP for most of the boats was done by Cavedude (BB to Tim Deep shuttles was the only route he didn't finish).

Database Settings[edit]

The following database tables are used:

  • npc_types
  • spawngroup
  • spawnentry
  • spawn2
  • spawn_conditions
  • grid
  • grid_entries

npc_types[edit]

Boats are implemented as NPCs, so you'll find them in the npc_types table with a race value of Launch (73). For example,

INSERT INTO `npc_types` (`id`, `name`, `lastname`, `level`, `race`, `class`, `bodytype`, `hp`, `mana`, `gender`, `texture`, `helmtexture`, `size`, `hp_regen_rate`, `mana_regen_rate`, `loottable_id`, `merchant_id`, `npc_spells_id`, `npc_spells_effects_id`, `npc_faction_id`, `mindmg`, `maxdmg`, `attack_count`, `special_abilities`, `aggroradius`, `assistradius`, `face`, `luclin_hairstyle`, `luclin_haircolor`, `luclin_eyecolor`, `luclin_eyecolor2`, `luclin_beardcolor`, `luclin_beard`, `armortint_id`, `armortint_red`, `armortint_green`, `armortint_blue`, `d_melee_texture1`, `d_melee_texture2`, `prim_melee_type`, `sec_melee_type`, `ranged_type`, `runspeed`, `MR`, `CR`, `DR`, `FR`, `PR`, `see_invis`, `see_invis_undead`, `qglobal`, `AC`, `npc_aggro`, `spawn_limit`, `attack_delay`, `STR`, `STA`, `DEX`, `AGI`, `_INT`, `WIS`, `CHA`, `see_sneak`, `see_improved_hide`, `ATK`, `Accuracy`, `slow_mitigation`, `maxlevel`, `scalerate`, `private_corpse`, `unique_spawn_by_name`, `underwater`, `isquest`, `emoteid`, `spellscale`, `healscale`, `raid_target`, `chesttexture`, `armtexture`, `bracertexture`, `handtexture`, `legtexture`, `feettexture`, `light`, `walkspeed`, `combat_hp_regen`, `combat_mana_regen`, `aggro_pc`, `ignore_distance`, `encounter`, `ignore_despawn`, `avoidance`, `exp_pct`, `greed`, `engage_notice`, `stuck_behavior`, `flymode`) VALUES (846, 'Shuttle_I', '', 50, 73, 1, 5, 9550, 0, 0, 1, 0, 6, 287, 0, 0, 0, 0, 0, 0, 0, 0, -1, '19,1^20,1^24,1^35,1', 60, 0, 0, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 28, 28, 7, 1.25, 35, 35, 25, 35, 25, 0, 1, 0, 190, 0, 0, 30, 75, 75, 75, 75, 75, 75, 75, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 100, 100, 0, 0, 0, 0, 0, 0, 0, 0, 1.25, 6, 0, 0, 1000, 0, 0, 0, 100, 0, 0, 0, -1);

spawngroup[edit]

INSERT INTO `spawngroup` (`id`, `name`, `spawn_limit`, `max_x`, `min_x`, `max_y`, `min_y`, `delay`, `mindelay`, `despawn`, `despawn_timer`, `rand_spawns`, `rand_respawntime`, `rand_variance`, `rand_condition_`, `wp_spawns`) VALUES (448054, 'butcher_Shuttle_IV_244', 0, 0, 0, 0, 0, 0, 15000, 0, 100, 0, 1200, 0, 0, 0);

spawnentry[edit]

INSERT INTO `spawnentry` (`spawngroupID`, `npcID`, `chance`, `mintime`, `maxtime`, `expansion`, `min_expansion`, `max_expansion`) VALUES (448054, 849, 100, 0, 0, 0, 0, 0);

spawn2[edit]

INSERT INTO `spawn2` (`id`, `spawngroupID`, `zone`, `x`, `y`, `z`, `heading`, `respawntime`, `variance`, `pathgrid`, `_condition`, `cond_value`, `enabled`, `animation`, `boot_respawntime`, `clear_timer_onboot`, `boot_variance`, `force_z`, `expansion`, `min_expansion`, `max_expansion`, `raid_target_spawnpoint`) VALUES (326525, 448054, 'butcher', 3595.000000, 530.000000, -12.000000, 224.000000, 570, 0, 0, 6, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0);

spawn_conditions[edit]

INSERT INTO `spawn_conditions` (`zone`, `id`, `value`, `onchange`, `name`) VALUES ('butcher', 6, 0, 2, 'Shuttle_IV');

grid[edit]

INSERT INTO `grid` (`id`, `zoneid`, `type`, `type2`) VALUES (16, 68, 4, 1);

grid_entries[edit]

INSERT INTO `grid_entries` (`gridid`, `zoneid`, `number`, `x`, `y`, `z`, `heading`, `pause`, `centerpoint`) VALUES (16, 68, 11, 3595, 491, -11.9, 0, 0, 0);

Complete Example[edit]

The following is a complete example of database changes that were needed to implement the four shuttles leaving BB docks to the Maiden's Voyage in Timorous Deep:

LUA Scripting[edit]

Events[edit]

  • event_spawn
  • event_waypoint_arrive
  • event_waypoint_depart
  • event_enter_zone
  • event_board_boat
  • event_leave_boat

Functions[edit]

  • GetBoatNPCID()
  • e.self:GetBoatName()
  • e.self:SetBoatID(uint32 boatid);
  • e.self:SetBoatName(const char* boatname);

void SendToBoat(bool messageonly)

  • ent:GetBoatID() or e.self:GetBoatID()

This function can be used from the entity or player namespace, and usually to compare to an integer boatid value.

Example Scripts[edit]