Go Back   EQEmulator Home > EQEmulator Forums > Development > Development::Server Code Submissions

 
 
Thread Tools Display Modes
Prev Previous Post   Next Post Next
  #1  
Old 04-18-2009, 12:34 AM
realityincarnate
Developer
 
Join Date: Dec 2007
Posts: 122
Default Controllable boats

It took a bit of messing with, but I finally got the player controllable boats to work (more or less) properly. The biggest issue is that the boats seem to be really picky about their starting coordinates before they let themselves be moved. All the boats in Lake Rathetear work properly, but the one in Tox forest only lets you turn it and the one in Qeynos Hills doesn't let you move it at all. I was able to get both these boats working by tinkering with their spawn coordinates, but then I updated my database and lost the working coords. The code also works with SOF, except that the boats spawn in slightly different positions, so even the Lake Rathe ones need to be moved a bit. Anyway, here's the server code:
Code:
Index: EQEmuServer/common/eq_packet_structs.h
===================================================================
--- EQEmuServer/common/eq_packet_structs.h	(revision 433)
+++ EQEmuServer/common/eq_packet_structs.h	(working copy)
@@ -3727,6 +3727,13 @@
 	uint16	GuildID;
 } GroupLFPMemberEntry;
 
+struct ControlBoat_Struct {
+/*000*/	uint32	boatId;		// entitylist id of the boat
+/*004*/	bool	TakeControl;	// 01 if taking control, 00 if releasing it
+/*007*/ 				// no idea what these last three bytes represent
+};
+
+
 //old structures live here:
 #include "eq_old_structs.h"
 
Index: EQEmuServer/utils/patch_Titanium.conf
===================================================================
--- EQEmuServer/utils/patch_Titanium.conf	(revision 433)
+++ EQEmuServer/utils/patch_Titanium.conf	(working copy)
@@ -258,7 +258,7 @@
 OP_ClientError=0x0000
 OP_DeleteItem=0x4d81
 OP_DeleteCharge=0x1c4a
-OP_ControlBoat=0x0000
+OP_ControlBoat=0x2c81
 OP_DumpName=0x0000
 OP_FeignDeath=0x7489
 OP_Heartbeat=0x0000
Index: EQEmuServer/zone/client.h
===================================================================
--- EQEmuServer/zone/client.h	(revision 433)
+++ EQEmuServer/zone/client.h	(working copy)
@@ -971,7 +971,7 @@
 	int16				weight;
 	bool				berserk;
 	bool				dead;
-	bool				IsOnBoat;
+	int16				BoatID;
 	bool				IsTracking;
 	int16				CustomerID;
 	bool	Trader;
Index: EQEmuServer/zone/client_packet.cpp
===================================================================
--- EQEmuServer/zone/client_packet.cpp	(revision 433)
+++ EQEmuServer/zone/client_packet.cpp	(working copy)
@@ -947,9 +947,29 @@
 	}
 	PlayerPositionUpdateClient_Struct* ppu = (PlayerPositionUpdateClient_Struct*)app->pBuffer;
 
-	if(ppu->spawn_id != GetID())
-		return;
+	if(ppu->spawn_id != GetID()) {
+		// check if the id is for a boat the player is controlling
+		if (ppu->spawn_id == BoatID) {
+			Mob* boat = entity_list.GetMob(BoatID);
+			if (boat == 0) {	// if the boat ID is invalid, reset the id and abort
+				BoatID = 0;
+				return;
+			}
 
+			// set the boat's position deltas
+			boat->SetDeltas(ppu->delta_x, ppu->delta_y, ppu->delta_z, ppu->delta_heading);
+			// send an update to everyone nearby except the client controlling the boat
+			EQApplicationPacket* outapp = new EQApplicationPacket(OP_ClientUpdate, sizeof(PlayerPositionUpdateServer_Struct));
+			PlayerPositionUpdateServer_Struct* ppus = (PlayerPositionUpdateServer_Struct*)outapp->pBuffer;
+			boat->MakeSpawnUpdate(ppus);
+			entity_list.QueueCloseClients(boat,outapp,true,300,this,false);
+			safe_delete(outapp);
+			// update the boat's position on the server, without sending an update
+			boat->GMMove(ppu->x_pos, ppu->y_pos, ppu->z_pos, EQ19toFloat(ppu->heading), false);
+			return;
+		}
+		else return;	// if not a boat, do nothing
+	}
 
 	float dist = 0;
 	float tmp;
@@ -3595,19 +3615,25 @@
 void Client::Handle_OP_BoardBoat(const EQApplicationPacket *app)
 {
 	char *boatname;
-	this->IsOnBoat=true;
-	boatname = new char[app->size-4];
-	memset(boatname, 0, app->size-4);
+	boatname = new char[app->size-3];
+	memset(boatname, 0, app->size-3);
 	memcpy(boatname, app->pBuffer, app->size-4);
-	printf("%s has gotten on the boat %s\n",GetName(),boatname);
-	//Mob* boat = entity_list.GetMob(boatname);
+	
+	Mob* boat = entity_list.GetMob(boatname);
+	if (boat)
+		this->BoatID = boat->GetID();	// set the client's BoatID to show that it's on this boat
 	safe_delete(boatname);
 	return;
 }
 
 void Client::Handle_OP_LeaveBoat(const EQApplicationPacket *app)
 {
-	this->IsOnBoat=false;
+	Mob* boat = entity_list.GetMob(this->BoatID);	// find the mob corresponding to the boat id
+	if (boat) {
+		if ((boat->GetTarget() == this) && boat->GetHateAmount(this) == 0)	// if the client somehow left while still controlling the boat (and the boat isn't attacking them)
+			boat->SetTarget(0);			// fix it to stop later problems
+	}
+	this->BoatID = 0;
 	return;
 }
 
@@ -6526,6 +6552,29 @@
 
 void Client::Handle_OP_ControlBoat(const EQApplicationPacket *app)
 {
+	ControlBoat_Struct* cbs = (ControlBoat_Struct*)app->pBuffer;
+	Mob* boat = entity_list.GetMob(cbs->boatId);
+	if (boat == 0) 
+		return;	// do nothing if the boat isn't valid
+	
+	if (cbs->TakeControl) {
+		// this uses the boat's target to indicate who has control of it.  It has to check hate to make sure the boat isn't actually attacking anyone.
+		if ((boat->GetTarget() == 0) || (boat->GetTarget() == this && boat->GetHateAmount(this) == 0)) {
+			boat->SetTarget(this);
+		}
+		else {
+			this->Message_StringID(13,IN_USE);
+			return;
+		}
+	}
+	else 
+		boat->SetTarget(0);
+		
+	EQApplicationPacket* outapp=new EQApplicationPacket(OP_ControlBoat,0);
+	FastQueuePacket(&outapp);
+	safe_delete(outapp);
+	// have the boat signal itself, so quests can be triggered by boat use
+	boat->CastToNPC()->SignalNPC(0);
 }
 
 void Client::Handle_OP_DumpName(const EQApplicationPacket *app)
Index: EQEmuServer/zone/mob.cpp
===================================================================
--- EQEmuServer/zone/mob.cpp	(revision 433)
+++ EQEmuServer/zone/mob.cpp	(working copy)
@@ -1088,7 +1088,7 @@
 	}
 }
 
-void Mob::GMMove(float x, float y, float z, float heading) {
+void Mob::GMMove(float x, float y, float z, float heading, bool SendUpdate) {
 	x_pos = x;
 	y_pos = y;
 	z_pos = z;
@@ -1096,7 +1096,8 @@
 		this->heading = heading;
 	if(IsNPC())
 		CastToNPC()->SaveGuardSpot(true);
-	SendAllPosition();
+	if(SendUpdate)
+		SendAllPosition();
 	//SendPosUpdate(1);
 #ifdef PACKET_UPDATE_MANAGER
 	if(IsClient()) {
@@ -4348,3 +4349,10 @@
 
 	return;
 }
+
+void Mob::SetDeltas(float dx, float dy, float dz, float dh) {
+	delta_x = dx;
+	delta_y = dy;
+	delta_z = dz;
+	delta_heading = dh;
+}
Index: EQEmuServer/zone/mob.h
===================================================================
--- EQEmuServer/zone/mob.h	(revision 433)
+++ EQEmuServer/zone/mob.h	(working copy)
@@ -428,7 +428,8 @@
 	void DoAnim(const int animnum, int type=0, bool ackreq = true, eqFilterType filter = FilterNone);
 
 	void ChangeSize(float in_size, bool bNoRestriction = false);
-	virtual void GMMove(float x, float y, float z, float heading = 0.01);
+	virtual void GMMove(float x, float y, float z, float heading = 0.01, bool SendUpdate = true);
+	void SetDeltas(float delta_x, float delta_y, float delta_z, float delta_h);
 	void SendPosUpdate(int8 iSendToSelf = 0);
 	void MakeSpawnUpdateNoDelta(PlayerPositionUpdateServer_Struct* spu);
 	void MakeSpawnUpdate(PlayerPositionUpdateServer_Struct* spu);
Index: EQEmuServer/zone/StringIDs.h
===================================================================
--- EQEmuServer/zone/StringIDs.h	(revision 433)
+++ EQEmuServer/zone/StringIDs.h	(working copy)
@@ -192,4 +192,5 @@
 #define SONG_NEEDS_STRINGS 407	// You need to play a stringed instrument for this song
 #define SONG_NEEDS_BRASS 408	// You need to play a brass instrument for this song
 #define NO_INSTRUMENT_SKILL 269	// "Stick to singing until you learn to play this instrument."
+#define IN_USE 1406	// "Someone else is using that.  Try again later."
 #endif
The other part of using the boats is making them return to their original positions after they haven't been used for a while. To make this work fairly simply (especially in lake rathe, where 7 boats share a quest file and an npctypeid), I made the server send a signal ($signalid = 0) to the boat when it gets a control request. When in use, the boat targets the player controlling it, otherwise it has no target. Here's the perl script I've been using to control boat respawns.
Code:
# Quest to return boats to their spawn points after an hour without use
# by RealityIncarnate

# The boat receives a signal (id 0) whenever the server gets a ControlBoat opcode
sub EVENT_SIGNAL {
  quest::stoptimer("respawn");		# stop any respawn countdown in progress
  quest::settimer("checkuse", 60);	# begin checking for use every minute
}

sub EVENT_TIMER {
  if($timer eq "checkuse") {
    if(!$npc->GetTarget()) {		# if the boat has a target, it's in use.  If not, begin the respawn timer
      quest::stoptimer("checkuse");	# stop checking for use
      quest::settimer("respawn",3600); 	# start the respawn timer at 1 hour
    }
  }
  if($timer eq "respawn") {
    quest::respawn($mobid,0);		# it hasn't been used for an hour, so respawn the boat		
  }
}
Reply With Quote
 


Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is On
HTML code is Off

Forum Jump

   

All times are GMT -4. The time now is 05:59 PM.


 

Everquest is a registered trademark of Daybreak Game Company LLC.
EQEmulator is not associated or affiliated in any way with Daybreak Game Company LLC.
Except where otherwise noted, this site is licensed under a Creative Commons License.
       
Powered by vBulletin®, Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
Template by Bluepearl Design and vBulletin Templates - Ver3.3