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

Reply
 
Thread Tools Display Modes
  #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
  #2  
Old 04-19-2009, 11:03 AM
janusd
Sarnak
 
Join Date: Jan 2008
Posts: 47
Default

Wait... you got the boat to target the player without the player having to initiate contact with an entry or some other such? Pretty sexy! I'd forgotten all about those boats, honestly.

You think it's possible to work up a PERL command to make NPCs respond to text without being targeted? Just when something is said within a certain radius of 'em they respond if they're keyed to respond to it?
Reply With Quote
  #3  
Old 04-19-2009, 01:07 PM
cavedude's Avatar
cavedude
The PEQ Dude
 
Join Date: Apr 2003
Location: -
Posts: 1,988
Default

This is in SVN, and I added a_boat.pl to the templates folder on the PEQ SVN. I'll work on the spawnpoints for the broken boats. Thanks!
Reply With Quote
  #4  
Old 04-19-2009, 11:34 PM
realityincarnate
Developer
 
Join Date: Dec 2007
Posts: 122
Default

Quote:
Originally Posted by janusd View Post
Wait... you got the boat to target the player without the player having to initiate contact with an entry or some other such? Pretty sexy! I'd forgotten all about those boats, honestly.

You think it's possible to work up a PERL command to make NPCs respond to text without being targeted? Just when something is said within a certain radius of 'em they respond if they're keyed to respond to it?
The boat targeting was fairly simple because the client sends a couple of different op codes when it enters or leaves boats, so I used those to trigger the changes.

I can think of a couple of ways that NPCs could respond to things said within a certain radius, but they all involve adding a lot of extra work for the server (as every npc sifts through everything that happens near them, looking for those few relevant items). At the very least, it would probably need a database flag, similar to the quest globals flag, to designate the few NPCs that need to do that.
Reply With Quote
  #5  
Old 04-20-2009, 07:37 AM
janusd
Sarnak
 
Join Date: Jan 2008
Posts: 47
Default

If it were coded to switch on an off as PCs enter or leave a certain radius of the npc that might make the server load a lot lighter.

The main reason I ask is that there are some Live quests that require proximity speech without a target. I'm also fairly certain that the custom admins could use a similar function to their own devious ends.
Reply With Quote
Reply

Thread Tools
Display Modes

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 07:01 AM.


 

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