diff --git a/source/src/Makefile b/source/src/Makefile index 9dc47028..dc27fed2 100644 --- a/source/src/Makefile +++ b/source/src/Makefile @@ -19,6 +19,7 @@ CLIENT_OBJS= \ renderextras.o \ rendergl.o \ rendermd2.o \ + rendermd3.o \ renderparticles.o \ rendertext.o \ rndmap.o \ @@ -34,13 +35,21 @@ CLIENT_OBJS= \ worldio.o \ worldlight.o \ worldocull.o \ - worldrender.o + worldrender.o \ + bot/bot.o \ + bot/botmanager.o \ + bot/bot_ai.o \ + bot/bot_util.o \ + bot/bot_waypoint.o \ + bot/ac_bot.o \ + bot/ac_bot_ai.o + SERVER_LIBS=-L../enet -lenet SERVER_OBJS= \ standalone.o \ serverms.o \ - server.o \ + servstndalone.o \ tools.o default: all @@ -51,14 +60,18 @@ enet: -make -C ../enet all clean: - -rm -f $(SERVER_OBJS) $(CLIENT_OBJS) ../../bin/ac_server ../../bin/ac_client + -rm -f $(SERVER_OBJS) $(CLIENT_OBJS) -make -C ../enet/ clean + standalone.o: $(CXX) $(CXXFLAGS) -DSTANDALONE -o standalone.o -c serverutil.cpp +servstndalone.o: + $(CXX) $(CXXFLAGS) -DSTANDALONE -o servstndalone.o -c server.cpp + client: $(CLIENT_OBJS) - $(CXX) $(CXXFLAGS) -o ../../bin/ac_client $(CLIENT_OBJS) $(CLIENT_LIBS) + $(CXX) $(CXXFLAGS) -g -o ../../bin/ac_client $(CLIENT_OBJS) $(CLIENT_LIBS) server: $(SERVER_OBJS) $(CXX) $(CXXFLAGS) -o ../../bin/ac_server $(SERVER_OBJS) $(SERVER_LIBS) diff --git a/source/src/clientextras.cpp b/source/src/clientextras.cpp index ef13609d..65b8d32e 100644 --- a/source/src/clientextras.cpp +++ b/source/src/clientextras.cpp @@ -15,7 +15,7 @@ void renderclient(dynent *d, bool team, char *mdlname, bool hellpig, float scale float speed = 100.0f; float mz = d->o.z-d->eyeheight+1.55f*scale; int basetime = -((int)d&0xFFF); - if(d->state==CS_DEAD) + /*if(d->state==CS_DEAD) { int r; if(hellpig) { n = 2; r = range[3]; } else { n = (int)d%3; r = range[n]; }; @@ -35,8 +35,10 @@ void renderclient(dynent *d, bool team, char *mdlname, bool hellpig, float scale else if((!d->move && !d->strafe) || !d->moving) { n = 12; } else if(!d->onfloor && d->timeinair>100) { n = 18; } else { n = 14; speed = 1200/d->maxspeed*scale; if(hellpig) speed = 300/d->maxspeed; }; - if(hellpig) { n++; scale *= 32; mz -= 1.9f; }; - rendermodel(mdlname, frame[n], range[n], 0, 1.5f, d->o.x, mz, d->o.y, d->yaw+90, d->pitch/2, team, scale, speed, 0, basetime); + if(hellpig) { n++; scale *= 32; mz -= 1.9f; };*/ + //rendermodel(mdlname, frame[n], range[n], 0, 1.5f, d->o.x, mz, d->o.y, d->yaw+90, d->pitch/2, team, scale, speed, 0, basetime); + + rendermd3player(d); }; extern int democlientnum; @@ -45,7 +47,7 @@ void renderclients() { dynent *d; loopv(players) - if((d = players[i]) && (!demoplayback || i!=democlientnum)) + if((d = players[i]) && (!demoplayback || i!=democlientnum) && players[i] != player1) { if (d->state==CS_DEAD) break; //cheap hack fix of @@ -105,12 +107,14 @@ void renderscores() scorelines.setsize(0); if(!demoplayback) renderscore(player1); loopv(players) if(players[i]) renderscore(players[i]); + BotManager.RenderBotScore(); // Added by Rick: Render Score for bots sortmenu(0, scorelines.length()); if(m_teammode) { teamsused = 0; loopv(players) addteamscore(players[i]); if(!demoplayback) addteamscore(player1); + BotManager.AddBotTeamScore(); // Added by Rick: Add team scores for bots teamscores[0] = 0; loopj(teamsused) { diff --git a/source/src/clientgame.cpp b/source/src/clientgame.cpp index 3e7cf7dc..93b9d925 100644 --- a/source/src/clientgame.cpp +++ b/source/src/clientgame.cpp @@ -44,34 +44,17 @@ void spawnstate(dynent *d) // reset player state not persistent acc d->timeinair = 0; d->health = 100; d->armour = 0; - //d->hasarmour = false; +// d->armourtype = A_BLUE; d->quadmillis = 0; d->gunselect = GUN_PISTOL; d->gunwait = 0; d->attacking = false; - if(d->primary==GUN_SNIPER && d->altattack) - setvar("fov",getvar("fov")+55); - d->altattack = false; d->lastaction = 0; - d->gunselect = d->primary; - - radd(d); - //loopi(NUMGUNS) if(d->nextprimary!=i) d->ammo[i] = 0; - //d->mag[GUN_KNIFE] = 1; + loopi(NUMGUNS) d->ammo[i] = 0; + d->ammo[GUN_KNIFE] = 1; //equp with default pistol - - //d->mag[GUN_PISTOL] = 8; - //if(d->nextprimary!=GUN_PISTOL) - // d->ammo[GUN_PISTOL] = 16; - //else - // d->ammo[GUN_PISTOL] = 32; - //temp for testing guns - //d->ammo[GUN_SHOTGUN] = 100; - //d->ammo[GUN_SUBGUN] = 400; - //d->ammo[GUN_SNIPER] = 100; - //d->ammo[GUN_ASSULT] = 400; - - //end tmp + d->ammo[GUN_PISTOL] = 24; + d->mag[GUN_PISTOL] = 12; /* if(m_noitems) @@ -114,8 +97,18 @@ void spawnstate(dynent *d) // reset player state not persistent acc d->ammo[GUN_SG] = 5; }; */ + + d->ammo[GUN_SHOTGUN] = 8; + d->mag[GUN_SHOTGUN] = 20; + + // Added by Rick + loopi(MAX_STORED_LOCATIONS) d->PrevLocations.Reset(); + // End add + + //add cases to switch setup default player in different modes here }; + dynent *newdynent() // create a new blank player or monster { dynent *d = (dynent *)gp()->alloc(sizeof(dynent)); @@ -146,6 +139,14 @@ dynent *newdynent() // create a new blank player or monster d->altattack = false; d->nextprimary = 1; d->nextarmour = false; + // edit: driAn + loopi(3) + { + d->mdl[i] = i; + d->animstate[i].frm = 0; + d->animstate[i].anim = 0; + d->animstate[i].lastTime = 0; + }; spawnstate(d); return d; }; @@ -165,15 +166,15 @@ void respawn() }; }; -void arenacount(dynent *d, int &alive, int &dead, char *&lastteam, char *&lastname, bool &oneteam) -//void arenacount(dynent *d, int &alive, int &dead, char *&lastteam, bool &oneteam) +//void arenacount(dynent *d, int &alive, int &dead, char *&lastteam, char *&lastname, bool &oneteam) +void arenacount(dynent *d, int &alive, int &dead, char *&lastteam, bool &oneteam) { if(d->state!=CS_DEAD) { alive++; if(lastteam && strcmp(lastteam, d->team)) oneteam = false; lastteam = d->team; - lastname = d->name; +// FIXME lastname = d->name; } else { @@ -195,6 +196,9 @@ void arenarespawn() arenarespawnwait = 0; conoutf("new round starting... fight!"); respawnself(); + // Added by Rick: Let all bots respawn if were the host + if (ishost()) BotManager.RespawnBots(); + //End add by Rick }; } else if(arenadetectwait==0 || arenadetectwait0 && (alive<=1 || (m_teammode && oneteam))) { conoutf("arena round is over! next round in 5 seconds..."); @@ -213,11 +218,11 @@ void arenarespawn() { if(m_teammode) conoutf("team %s has won the round", (int)lastteam); - else - conoutf("%s is the survior!", (int)lastname); +// FIXME else +// FIXME conoutf("%s is the survior!", (int)lastname); } else conoutf("everyone died!"); - arenarespawnwait = lastmillis+5000; +// FIXME arenarespawnwait = int health, armour, armourtype, quadmillis;lastmillis+5000; arenadetectwait = lastmillis+10000; player1->roll = 0; }; @@ -255,6 +260,17 @@ void otherplayers() }; if(lagtime && players[i]->state != CS_DEAD && (!demoplayback || i!=democlientnum)) moveplayer(players[i], 2, false); // use physics to extrapolate player position }; + + // Added by Rick + if (!ishost()) + { + loopv(bots) + { + if(bots[i] && bots[i]->state != CS_DEAD && (!demoplayback)) + moveplayer(bots[i], 2, false); // use physics to extrapolate bot position + } + } + // End add }; int sleepwait = 0; @@ -282,6 +298,11 @@ void updateworld(int millis) // main game update loop otherplayers(); if(!demoplayback) { + //monsterthink(); + + // Added by Rick: let bots think + BotManager.Think(); + //put game mode extra call here if(player1->state==CS_DEAD) { @@ -294,7 +315,7 @@ void updateworld(int millis) // main game update loop else if(!intermission) { moveplayer(player1, 20, true); - checkitems(); + //FIXME checkitems(); }; c2sinfo(player1); // do this last, to reduce the effective frame lag }; @@ -403,6 +424,10 @@ void selfdamage(int damage, int actor, dynent *act) player1->roll += player1->roll>0 ? droll : (player1->roll<0 ? -droll : (rnd(2) ? droll : -droll)); // give player a kick depending on amount of damage if((player1->health -= damage)<=0) { + // Added by Rick + if (act->bIsBot && (actor == -2)) + actor = BotManager.GetBotIndex(act); + if(actor==-2) { conoutf("you got killed by %s!", (int)&act->name); @@ -415,7 +440,13 @@ void selfdamage(int damage, int actor, dynent *act) } else { - dynent *a = getclient(actor); + // Modified by Rick + //dynent *a = getclient(actor); + dynent *a; + if (act->bIsBot) a = act; + else a = getclient(actor); + // End mod + if(a) { if(isteam(a->team, player1->team)) @@ -429,7 +460,8 @@ void selfdamage(int damage, int actor, dynent *act) }; }; showscores(true); - addmsg(1, 2, SV_DIED, actor); + // addmsg(1, 2, SV_DIED, actor); Modified by Rick: Notify if the killer was a bot + addmsg(1, 3, SV_DIED, actor, act->bIsBot); player1->lifesequence++; player1->attacking = false; player1->state = CS_DEAD; @@ -441,6 +473,9 @@ void selfdamage(int damage, int actor, dynent *act) playsound(S_DIE1+rnd(2)); //add suicide sound spawnstate(player1); player1->lastaction = lastmillis; + // Added by Rick: If this player is killed by a bot, update the bots frag count + if (act->bIsBot) + addmsg(1, 3, SV_BOTFRAGS, BotManager.GetBotIndex(act), ++act->frags); } else { @@ -475,6 +510,30 @@ dynent *getclient(int cn) // ensure valid entity return players[cn] ? players[cn] : (players[cn] = newdynent()); }; +// Added by Rick +dynent *getbot(int cn) // ensure valid entity +{ + if(cn<0 || cn>=MAXCLIENTS) + { + neterr("botnum"); + return NULL; + }; + + while(cn>=bots.length()) bots.add(NULL); + if (!bots[cn]) + { + bots[cn] = newdynent(); + if (bots[cn]) + { + bots[cn]->pBot = NULL; + bots[cn]->bIsBot = true; + } + } + + return bots[cn]; +}; +// End add by Rick + void initclient() { clientmap[0] = 0; @@ -485,7 +544,10 @@ void startmap(char *name) // called just after a map load { //if(netmapstart()) { gamemode = 0;}; //needs fixed to switch modes? sleepwait = 0; - //put call to clear/restart game mode extras here + //monsterclear(); + // Added by Rick + BotManager.BeginMap(name); + // End add by Rick projreset(); spawncycle = -1; spawnplayer(player1); diff --git a/source/src/clients2c.cpp b/source/src/clients2c.cpp index d462553d..6e6da3b5 100644 --- a/source/src/clients2c.cpp +++ b/source/src/clients2c.cpp @@ -24,6 +24,29 @@ void changemap(char *name) // request map change, server ma strcpy_s(toservermap, name); }; +// Added by Rick +void botcommand(uchar *&p, char *text) +{ + int type = getint(p); + switch(EBotCommands(type)) + { + case COMMAND_ADDBOT: + getint(p); + getint(p); + sgetstr(); + sgetstr(); + break; + case COMMAND_KICKBOT: + if (getint(p)==1) // Kick a specific bot + sgetstr(); + break; + case COMMAND_BOTSKILL: + getint(p); + break; + } +} +// End add + // update the position of other clients in the game in our world // don't care if he's in the scenery or other players, // just don't overlap with our client @@ -156,6 +179,11 @@ void localservertoclient(uchar *buf, int len) // processes any updates from th { c2sinit = false; // send new players my info again conoutf("connected: %s", (int)&text); + // Added by Rick: If we are the host("the bot owner"), tell the bots + // to update their stats + if (ishost()) + BotManager.LetBotsUpdateStats(); + // End add by Rick }; strcpy_s(d->name, text); sgetstr(); @@ -182,7 +210,12 @@ void localservertoclient(uchar *buf, int len) // processes any updates from th e.y = getint(p)/DMF; e.z = getint(p)/DMF; if(gun==GUN_SHOTGUN) createrays(s, e); - shootv(gun, s, e, d); + // Added by Rick: Check if its a player or bot + int index = getint(p); + dynent *player = (index == -1) ? d : getbot(index); + // shootv(gun, s, e, d); Modified by Rick + shootv(gun, s, e, player); + // End add/mod break; }; @@ -191,7 +224,20 @@ void localservertoclient(uchar *buf, int len) // processes any updates from th int target = getint(p); int damage = getint(p); int ls = getint(p); - if(target==clientnum) { if(ls==player1->lifesequence) selfdamage(damage, cn, d); } + + // Added by Rick + bool dmgbybot = getint(p); // Damaged by a bot? + int damager = getint(p); // Who is the damager? + // End add + + // Modified by Rick: Check if a bot damaged this player + //if(target==clientnum) { if(ls==player1->lifesequence) selfdamage(damage, cn, d); } + if(target==clientnum) + { + dynent *a = (dmgbybot) ? getbot(damager) : d; + if(ls==player1->lifesequence) + selfdamage(damage, cn, a); + } else playsound(S_PAIN1+rnd(5), &getclient(target)->o); break; }; @@ -199,11 +245,15 @@ void localservertoclient(uchar *buf, int len) // processes any updates from th case SV_DIED: { int actor = getint(p); - if(actor==cn) + int KilledByBot = getint(p); // Added by Rick + + // if(actor==cn) Modified by Rick + if((actor==cn) && !KilledByBot) { conoutf("%s suicided", (int)d->name); } - else if(actor==clientnum) + // else if(actor==clientnum) Modified by Rick + else if((actor==clientnum) && !KilledByBot) { int frags; if(isteam(player1->team, d->team)) @@ -220,7 +270,9 @@ void localservertoclient(uchar *buf, int len) // processes any updates from th } else { - dynent *a = getclient(actor); + // dynent *a = getclient(actor); Modifiied by Rick + dynent *a = (KilledByBot) ? getbot(actor) : getclient(actor); + // End mod if(a) { if(isteam(a->team, d->name)) @@ -245,6 +297,7 @@ void localservertoclient(uchar *buf, int len) // processes any updates from th case SV_ITEMPICKUP: setspawn(getint(p), false); getint(p); + getint(p); // Added by Rick break; case SV_ITEMSPAWN: @@ -343,6 +396,186 @@ void localservertoclient(uchar *buf, int len) // processes any updates from th for(int n = getint(p); n; n--) getint(p); break; }; + + // Added by Rick: Bot specific messages + case SV_BOTSOUND: + { + int Sound = getint(p); + dynent *pBot = getbot(getint(p)); + + if (pBot) + playsound(Sound, &pBot->o); + + break; + } + + case SV_ADDBOT: // Bot joined the game + { + dynent *b = getbot(getint(p)); + if (!b) break; + + sgetstr(); + if(b->name[0]) // already connected + { + if(strcmp(b->name, text)) + conoutf("%s is now known as %s", (int)b->name, (int)&text); + } + else // new client + { + //c2sinit = false; // send new players my info again + conoutf("connected: %s", (int)&text); + }; + strcpy_s(b->name, text); + sgetstr(); + strcpy_s(b->team, text); + b->lifesequence = getint(p); + b->bIsBot = true; + b->pBot = NULL; + break; + }; + + case SV_BOTDIS: + { + int n = getint(p); + dynent *b = getbot(n); + + if (!b) + break; + + conoutf("bot %s disconnected", (int)(b->name[0] ? b->name : "[incompatible client]")); + delete b->pBot; + zapdynent(bots[n]); + bots.remove(n); + break; + } + + case SV_CLIENT2BOTDMG: + case SV_BOT2BOTDMG: + { + int target = getint(p); + int damage = getint(p); + int damager = getint(p); + dynent *b = getbot(target); + + dynent *a; + if (damager == -1) + { + // HACK! if the local client who sended the message is the damager, its -1 + a = d; + } + else if (type == SV_CLIENT2BOTDMG) + { + if (damager == clientnum) a = player1; + else a = getclient(damager); + } + else + a = getbot(damager); + + if (b && a) + { + // Do we know the bot info? if so we are the host... + if (b->pBot) b->pBot->BotPain(damage, a); + playsound(S_PAIN1+rnd(5), &b->o); + } + break; + } + + case SV_BOTDIED: + { + int b = getint(p); + int killer = getint(p); + bool KilledByABot = getint(p); + dynent *bot = getbot(b); + + if((b==killer) && KilledByABot) + { + conoutf("%s suicided", (int)bot->name); + } + else if((killer==clientnum) && !KilledByABot) + { + int frags; + if(isteam(player1->team, bot->team)) + { + frags = -1; + conoutf("you fragged a teammate (%s)", (int)bot->name); + } + else + { + frags = 1; + conoutf("you fragged %s", (int)bot->name); + }; + addmsg(1, 2, SV_FRAGS, player1->frags += frags); + } + else + { + dynent *k; + if (KilledByABot) + k = getbot(killer); + else if (killer == -1) + // if killer = -1, 'a player1' sended the message(hack) + k = d; + else + k = getclient(killer); + + if(bot && k) + { + if(isteam(bot->team, k->name)) + { + conoutf("%s fragged his teammate (%s)", (int)bot->name, (int)k->name); + } + else + { + conoutf("%s fragged %s", (int)bot->name, (int)k->name); + } + } + } + + playsound(S_DIE1+rnd(2), &bot->o); + bot->lifesequence++; + break; + }; + + case SV_BOTFRAGS: + { + dynent *b = getbot(getint(p)); + if (b) b->frags = getint(p); + break; + } + + case SV_BOTUPDATE: + { + int n = getint(p); + dynent *b = getbot(n); + if(!b) return; + b->o.x = getint(p)/DMF; + b->o.y = getint(p)/DMF; + b->o.z = getint(p)/DMF; + b->yaw = getint(p)/DAF; + b->pitch = getint(p)/DAF; + b->roll = getint(p)/DAF; + b->vel.x = getint(p)/DVF; + b->vel.y = getint(p)/DVF; + b->vel.z = getint(p)/DVF; + int f = getint(p); + b->strafe = (f&3)==3 ? -1 : f&3; + f >>= 2; + b->move = (f&3)==3 ? -1 : f&3; + b->onfloor = (f>>2)&1; + int state = f>>3; + if(state==CS_DEAD && b->state!=CS_DEAD) b->lastaction = lastmillis; + b->state = state; + + if (!b->bIsBot) + b->bIsBot = true; + + if(!demoplayback) updatepos(b); + break; + } + case SV_BOTCOMMAND: + botcommand(p, text); + break; + + // End add default: neterr("type"); diff --git a/source/src/cube.h b/source/src/cube.h index 025d1ac2..74c0ce9d 100644 --- a/source/src/cube.h +++ b/source/src/cube.h @@ -35,6 +35,7 @@ enum // hardcoded texture numbers DEFAULT_CEIL }; +//FIXME enum // static entity types { NOTUSED = 0, // entity slot not in use in map @@ -45,6 +46,20 @@ enum // static entity types MAPMODEL, // attr1 = angle, attr2 = idx CARROT, // attr1 = tag, attr2 = type LADDER, + OBJ_ITEM, + MONSTER, + TRIGGER, + OBJ_SITE, + I_AUTOPISTOL, + I_SHOTGUN, + I_SNIPER, + I_SUBGUN, + I_CARBINE, + I_SEMIRIFLE, + I_AUTORIFLE, + I_HELMET, + A_YELLOW, + I_SEMIPISTOL, MAXENTTYPES }; @@ -90,7 +105,79 @@ struct vec { float x, y, z; }; struct block { int x, y, xs, ys; }; struct mapmodelinfo { int rad, h, zoff, snap; char *name; }; -enum { GUN_KNIFE = 0, GUN_PISTOL, GUN_SHOTGUN, GUN_SUBGUN, GUN_SNIPER, GUN_ASSULT, GUN_GRENADE, NUMGUNS }; +enum { GUN_KNIFE = 0, GUN_PISTOL, GUN_SEMIPISTOL, GUN_SHOTGUN, GUN_SUBGUN, GUN_SNIPER, GUN_ASSULT, GUN_GRENADE, NUMGUNS }; + +enum { MDL_LOWER, MDL_UPPER, MDL_HEAD, MDL_WEAPON }; + +// Added by Rick +class CBot; + +#define MAX_STORED_LOCATIONS 7 + +extern int lastmillis; // last time (Moved up by Rick) + +class CPrevLocation +{ + int nextupdate; + +public: + vec prevloc[MAX_STORED_LOCATIONS]; + + CPrevLocation(void) : nextupdate(0) { Reset(); }; + void Reset(void) { loopi(MAX_STORED_LOCATIONS) prevloc[i].x=prevloc[i].y=prevloc[i].z=0.0f; }; + void Update(const vec &o) + { + extern float GetDistance(vec v1, vec v2); + + if (nextupdate > lastmillis) return; + if (GetDistance(o, prevloc[0]) >= 4.0f) + { + for(int i=(MAX_STORED_LOCATIONS-1);i>=1;i--) + prevloc[i] = prevloc[i-1]; + prevloc[0] = o; + } + nextupdate = lastmillis + 100; + }; +}; + +struct itemstat { int add, max, sound; }; +// End add + +struct md3state +{ + int anim; + int frm; + int lastTime; +}; + +enum // md3 animations +{ + BOTH_DEATH1 = 0, + BOTH_DEAD1, + BOTH_DEATH2, + BOTH_DEAD2, + BOTH_DEATH3, + BOTH_DEAD3, + TORSO_GESTURE, + TORSO_ATTACK, + TORSO_ATTACK2, + TORSO_DROP, + TORSO_RAISE, + TORSO_STAND, + TORSO_STAND2, + LEGS_WALKCR, + LEGS_WALK, + LEGS_RUN, + LEGS_BACK, + LEGS_SWIM, + LEGS_JUMP, + LEGS_LAND, + LEGS_JUMPB, + LEGS_LANDB, + LEGS_IDLE, + LEGS_IDLECR, + LEGS_TURN +}; struct dynent // players & monsters { @@ -108,7 +195,7 @@ struct dynent // players & monsters int lifesequence; // sequence id for each respawn, used in damage test int state; // one of CS_* below int frags; - int health, armour, quadmillis; //removed armour type + int health, armour, quadmillis, armourtype; //removed armour type int gunselect, gunwait; int lastaction, lastattackgun, lastmove; bool attacking; @@ -117,6 +204,9 @@ struct dynent // players & monsters int monsterstate; // one of M_* below, M_NONE means human int mtype; // see monster.cpp dynent *enemy; // monster wants to kill this entity + // Added by Rick: targetpitch + float targetpitch; // monster wants to look in this direction + // End add float targetyaw; // monster wants to look in this direction bool blocked, moving; // used by physics to signal ai int trigger; // millis at which transition to another monsterstate takes place @@ -132,8 +222,26 @@ struct dynent // players & monsters bool hasarmour; bool nextarmour; bool playerblocked; + bool onladder; + int mdl[3]; // which models + md3state animstate[3]; // which animations + // Added by Rick + CBot *pBot; // Only used if this is a bot, points to the bot class if we are the host, + // for other clients its NULL + bool bIsBot; // Is this dynent a bot? + CPrevLocation PrevLocations; // Previous stored locations of this player + // End add by Rick }; +// Moved from server.cpp by Rick +struct server_entity // server side version of "entity" type +{ + bool spawned; + int spawnsecs; +}; +// End move + + #define SAVEGAMEVERSION 6 // bump if dynent/netprotocol changes or any other savegame/demo data bumped from 5 //enum { A_BLUE, A_GREEN, A_YELLOW }; // armour types... take 20/40/60 % off @@ -155,6 +263,10 @@ enum SV_PING, SV_PONG, SV_CLIENTPING, SV_GAMEMODE, SV_EDITH, SV_EDITT, SV_EDITS, SV_EDITD, SV_EDITE, SV_SENDMAP, SV_RECVMAP, SV_SERVMSG, SV_ITEMLIST, + // Added by Rick: Bot specific messages + SV_BOTSOUND, SV_BOTDIS, SV_BOTDIED, SV_CLIENT2BOTDMG, SV_BOT2BOTDMG, + SV_BOTFRAGS, SV_ADDBOT, SV_BOTUPDATE, SV_BOTCOMMAND, + // End add SV_EXT, }; @@ -180,6 +292,14 @@ enum S_FLAUNCH, S_FEXPLODE, S_SPLASH1, S_SPLASH2, S_GRUNT1, S_GRUNT2, S_RUMBLE, + S_PAINO, + S_PAINR, S_DEATHR, + S_PAINE, S_DEATHE, + S_PAINS, S_DEATHS, + S_PAINB, S_DEATHB, + S_PAINP, S_PIGGR2, + S_PAINH, S_DEATHH, + S_PAIND, S_DEATHD, S_NULL }; @@ -202,7 +322,7 @@ extern dvector players; // all the other clients (in multiplayer extern bool editmode; extern vector ents; // map entities extern vec worldpos; // current target of the crosshair in the world -extern int lastmillis; // last time +//extern int lastmillis; // last time (Moved up by Rick) extern int curtime; // current frame time extern int gamemode, nextmode; extern int xtraverts; @@ -230,6 +350,9 @@ extern bool demoplayback; #define vdist(d,v,e,s) vec v = s; vsub(v,e); float d = (float)sqrt(dotprod(v,v)); #define vreject(v,u,max) ((v).x>(u).x+(max) || (v).x<(u).x-(max) || (v).y>(u).y+(max) || (v).y<(u).y-(max)) #define vlinterp(v,f,u,g) { (v).x = (v).x*f+(u).x*g; (v).y = (v).y*f+(u).y*g; (v).z = (v).z*f+(u).z*g; } +// Added by Rick (compares 2 vectors) +#define vis(v1,v2) ((v1.x==v2.x) && (v1.y==v2.y) && (v1.z==v2.z)) +// End add by Rick #define sgetstr() { char *t = text; do { *t = getint(p); } while(*t++); } // used by networking @@ -250,9 +373,9 @@ extern bool demoplayback; #define m_tarena (gamemode==4) #define m_teammode (gamemode==0 || (gamemode>=4 & !m_pistols)) #define m_ctf (gamemode==5) -//#define m_sp (gamemode<0) -//#define m_dmsp (gamemode==-1) -//#define m_classicsp (gamemode==-2) +#define m_sp (gamemode<0) +#define m_dmsp (gamemode==-1) +#define m_classicsp (gamemode==-2) #define isteam(a,b) (m_teammode && strcmp(a, b)==0) enum // function signatures for script functions, see command.cpp @@ -296,5 +419,19 @@ enum // function signatures for script functions, see command.cpp #include +// Added by Rick +extern ENetHost *clienthost; +inline bool ishost(void) { return !clienthost; }; + +void splaysound(int n, vec *loc=0); +void addteamscore(dynent *d); +void renderscore(dynent *d); +extern void conoutf(const char *s, int a = 0, int b = 0, int c = 0); // Moved from protos.h +extern void particle_trail(int type, int fade, vec &from, vec &to); // Moved from protos.h +extern bool listenserv; +extern bool intermission; +#include "bot/bot.h" +// End add by Rick + #include "protos.h" // external function decls diff --git a/source/src/entities.cpp b/source/src/entities.cpp index 60bbed1a..39d1d256 100644 --- a/source/src/entities.cpp +++ b/source/src/entities.cpp @@ -57,21 +57,32 @@ void renderentities() }; }; -struct itemstat { int add, max, sound; } itemstats[] = +//edit these items +// struct itemstat { int add, max, sound; } itemstats[] = Modified by Rick +itemstat itemstats[] = { - 7, 14, S_ITEMAMMO, //shotgun - 30, 90, S_ITEMAMMO, //subgun - 5, 15, S_ITEMAMMO, //sniper - 20, 40, S_ITEMAMMO, //assult - 5, 10, S_ITEMAMMO, //pistol - 1, 2, S_ITEMAMMO, //grenade - 25, 100, S_ITEMHEALTH, //health - 100, 125, S_ITEMARMOUR, //armour - 20000, 30000, S_ITEMPUP, //powerup + 10, 50, S_ITEMAMMO, //semipistol + 20, 100, S_ITEMAMMO, //autopistol + 5, 25, S_ITEMAMMO, //shotgun + 5, 25, S_ITEMAMMO, //sniper + 25, 100, S_ITEMAMMO, //subgun + 50, 200, S_ITEMAMMO, //carbine + 100, 100, S_ITEMAMMO, //semigun + 150, 150, S_ITEMAMMO, //autorifle + 1, 1, S_ITEMAMMO, //grenade + 150, 150, S_ITEMARMOUR, //helmet + 100, 100, S_ITEMARMOUR, //armour + 20000, 30000, S_ITEMPUP, //bomb + + }; void baseammo(int gun) { player1->ammo[gun] = itemstats[gun-1].add*2; }; +// Added by Rick: baseammo for bots +void botbaseammo(int gun, dynent *d) { d->ammo[gun] = itemstats[gun-1].add*2; }; +// End add + // these two functions are called when the server acknowledges that you really // picked up the item (in multiplayer someone may grab it before you). @@ -155,6 +166,17 @@ void pickup(int n, dynent *d) trigger(ents[n].attr1, ents[n].attr2, false); // needs to go over server for multiplayer break; + case OBJ_ITEM: + additem(n, d->quadmillis, 60); + break; + + case TRIGGER: + ents[n].spawned = false; + triggertime = lastmillis; + trigger(ents[n].attr1, ents[n].attr2, false); // needs to go over server for multiplayer + BotManager.PickNextTrigger(); // Added by Rick + break; + }; }; @@ -175,7 +197,7 @@ void checkitems() void putitems(uchar *&p) // puts items in network stream and also spawns them locally { - loopv(ents) if((ents[i].type>=I_AMMO && ents[i].type<=I_QUAD) || ents[i].type==CARROT) + loopv(ents) if((ents[i].type>=I_AMMO && ents[i].type<=I_QUAD) || ents[i].type==TRIGGER) { putint(p, i); ents[i].spawned = true; @@ -244,4 +266,45 @@ void add(int num) }; }; -COMMAND(add,ARG_1INT); \ No newline at end of file +COMMAND(add,ARG_1INT); + +// Added by Rick +bool intersect(entity *e, vec &from, vec &to, vec *end) // if lineseg hits entity bounding box(entity version) +{ + mapmodelinfo &mmi = getmminfo(e->attr2); + if(!&mmi || !mmi.h) return false; + + float lo = (float)(S(e->x, e->y)->floor+mmi.zoff+e->attr3); + float hi = lo+mmi.h; + vec v = to, w = { e->x, e->y, lo + (fabs(hi-lo)/2.0f) }, *p; + vsub(v, from); + vsub(w, from); + float c1 = dotprod(w, v); + + if(c1<=0) p = &from; + else + { + float c2 = dotprod(v, v); + if(c2<=c1) p = &to; + else + { + float f = c1/c2; + vmul(v, f); + vadd(v, from); + p = &v; + }; + }; + + if (p->x <= e->x+mmi.rad + && p->x >= e->x-mmi.rad + && p->y <= e->y+mmi.rad + && p->y >= e->y-mmi.rad + && p->z <= hi + && p->z >= lo) + { + if (end) *end = *p; + return true; + } + return false; +}; +// End add by Ricks diff --git a/source/src/physics.cpp b/source/src/physics.cpp index 8c808596..bf9c16a6 100644 --- a/source/src/physics.cpp +++ b/source/src/physics.cpp @@ -5,8 +5,23 @@ #include "cube.h" -/* Modified by Rick: Fix, so we can jump on players aswell*/ -bool plcollide(dynent *d, dynent *o, float &headspace, float &hi, float &lo) // collide with player or monster +/* Modified by Rick: Now you can normally walk/jump/whatever on players +bool plcollide(dynent *d, dynent *o, float &headspace) // collide with player or monster +{ + if(o->state!=CS_ALIVE) return true; + const float r = o->radius+d->radius; + if(fabs(o->o.x-d->o.x)o.y-d->o.y)o.z-d->o.z)aboveeye+d->eyeheight) return false; + if(d->monsterstate) return false; // hack + headspace = d->o.z-o->o.z-o->aboveeye-d->eyeheight; + if(headspace<0) headspace = 10; + }; + return true; +}; +*/ + +bool plcollide(dynent *d, dynent *o, float &headspace, float &hi, float &lo) // collide with player or monster { if(o->state!=CS_ALIVE) return true; const float r = o->radius+d->radius; @@ -22,6 +37,7 @@ bool plcollide(dynent *d, dynent *o, float &headspace, float &hi, float &lo) }; return true; }; +// End mod bool cornertest(int mip, int x, int y, int dx, int dy, int &bx, int &by, int &bs) // recursively collide with a mipmapped corner cube { @@ -115,31 +131,38 @@ bool collide(dynent *d, bool spawn, float drop, float rise) // Modified by Rick: plcollide now takes hi and lo in account aswell, that way we can jump/walk on players - float headspace = 10; - // Added by Rick: don't collide with players/bots when spectating - if (d->state != CS_DEAD || (lastmillis-d->lastaction<2000)) - { - loopv(players) // collide with other players - { - dynent *o = players[i]; - if(!o || o==d) continue; - if(!plcollide(d, o, headspace, hi, lo)) { d->playerblocked = true; return false; } - }; + // Modified by Rick: plcollide now takes hi and lo in account aswell, that way we can + // jump/walk on players - if(d!=player1) if(!plcollide(d, player1, headspace, hi, lo)) { d->playerblocked = true; return false; } - //dvector &v = getmonsters(); - // this loop can be a performance bottleneck with many monster on a slow cpu, - // should replace with a blockmap but seems mostly fast enough - // Modified by Rick: Added v[i] pointer check - //loopv(v) if(v[i] && !vreject(d->o, v[i]->o, 7.0f) && d!=v[i] && !plcollide(d, v[i], headspace, hi, lo)) return false; - } + float headspace = 10; + loopv(players) // collide with other players + { + dynent *o = players[i]; + if(!o || o==d) continue; + if(!plcollide(d, o, headspace, hi, lo)) return false; + }; + + loopv(bots) // Added by Rick: collide with other bots + { + dynent *o = bots[i]; + if(!o || o==d) continue; + if(!plcollide(d, o, headspace, hi, lo)) return false; + }; // End add + + if(d!=player1) if(!plcollide(d, player1, headspace, hi, lo)) return false; + //dvector &v = getmonsters(); + // this loop can be a performance bottleneck with many monster on a slow cpu, + // should replace with a blockmap but seems mostly fast enough + //loopv(v) if(!vreject(d->o, v[i]->o, 7.0f) && d!=v[i] && !plcollide(d, v[i], headspace, hi, lo)) return false; + headspace -= 0.01f; mmcollide(d, hi, lo); // collide with map models if(spawn) - { + { d->o.z = lo+d->eyeheight; // just drop to floor (sideeffect) + d->startheight = d->o.z; d->onfloor = true; } else @@ -147,8 +170,16 @@ bool collide(dynent *d, bool spawn, float drop, float rise) const float space = d->o.z-d->eyeheight-lo; if(space<0) { - if(space>-0.01) d->o.z = lo+d->eyeheight; // stick on step - else if(space>-1.26f) d->o.z += rise; // rise thru stair + + if(space>-0.01) //stick to step + { + d->o.z = lo+d->eyeheight; + } + else if(space>-1.26f) //rise thru stair + { + d->o.z += rise; + + } else return false; } else @@ -167,9 +198,12 @@ bool collide(dynent *d, bool spawn, float drop, float rise) d->onfloor = d->o.z-d->eyeheight-lo<0.01f; }; return true; -} +}; -float rad(float x) { return x*3.14159f/180; }; +float rad(float x) +{ + return x*3.14159f/180; +}; VAR(maxroll, 0, 3, 20); @@ -195,10 +229,10 @@ void physicsframe() // optimally schedule physics frames inside the gra // local is false for multiplayer prediction void moveplayer(dynent *pl, int moveres, bool local, int curtime) -{ +{ const bool water = hdr.waterlevel>pl->o.z-0.5f; const bool floating = (editmode && local) || pl->state==CS_EDITING; - + vec d; // vector of direction we ideally want to move in d.x = (float)(pl->move*cos(rad(pl->yaw-90))); @@ -227,44 +261,66 @@ void moveplayer(dynent *pl, int moveres, bool local, int curtime) vmul(d, speed); // d is now frametime based velocity vector pl->blocked = false; - pl->playerblocked = false; // Added by Rick pl->moving = true; if(floating) // just apply velocity { vadd(pl->o, d); - if(pl->jumpnext) { pl->jumpnext = false; pl->vel.z = 2; } - } + if(pl->jumpnext) { pl->jumpnext = false; pl->vel.z = 2; } + } else // apply velocity with collision { - if(pl->onfloor || water) + if (pl->onfloor || !pl->vel.z) { - if(pl->jumpnext) + if ((int)pl->startheight - (int)pl->o.z > 8 && !pl->onladder && local) //try adding "&& local" if falling not fixed + selfdamage(abs((int)pl->startheight - (int)pl->o.z),-1,pl); + pl->startheight=pl->o.z; + }; + + if(pl->onfloor || water) + { + + if(pl->jumpnext && !pl->onladder) { pl->jumpnext = false; - pl->vel.z = 1.7f; // physics impulse upwards + pl->vel.z = 2.00f; // physics impulse upwards original 1.7f if(water) { pl->vel.x /= 8; pl->vel.y /= 8; }; // dampen velocity change even harder, gives correct water feel if(local) playsoundc(S_JUMP); else if(pl->monsterstate) playsound(S_JUMP, &pl->o); + else if(pl->bIsBot) botplaysound(S_JUMP, pl); // Added by Rick } - else if(pl->timeinair>800) // if we land after long time must have been a high jump, make thud sound + if (!pl->jumpnext && pl->onladder) { - if(local) playsoundc(S_LAND); - else if(pl->monsterstate) playsound(S_LAND, &pl->o); + if(pl->k_up) + pl->vel.z = 0.5f; }; + pl->timeinair = 0; } else { + if (pl->onladder) + { + pl->vel.z = 0; + if(pl->k_up) + pl->vel.z = 0.5f; + if(pl->k_down) + pl->vel.z = -0.5f; + }; + pl->timeinair += curtime; }; - + const float gravity = 20; - const float f = 1.0f/moveres; + const float f = 1.0f/moveres; float dropf = ((gravity-1)+pl->timeinair/15.0f); // incorrect, but works fine if(water) { dropf = 5; pl->timeinair = 0; }; // float slowly down in water - const float drop = dropf*curtime/gravity/100/moveres; // at high fps, gravity kicks in too fast - const float rise = speed/moveres/1.2f; // extra smoothness when lifting up stairs + if(pl->onladder) { dropf = 0; pl->timeinair = 0;}; + + // at high fps, gravity kicks in too fast + const float drop = dropf*curtime/gravity/100/moveres; + const float rise = speed/moveres/1.2f; + loopi(moveres) // discrete steps collision detection & sliding { @@ -287,7 +343,7 @@ void moveplayer(dynent *pl, int moveres, bool local, int curtime) pl->o.x -= f*d.x; pl->o.y -= f*d.y; if(collide(pl, false, drop, rise)) { d.y = d.x = 0; continue; }; - pl->o.z -= f*d.z; + pl->o.z -= f*d.z; break; }; }; @@ -319,11 +375,14 @@ void moveplayer(dynent *pl, int moveres, bool local, int curtime) if(pl->roll<-maxroll) pl->roll = (float)-maxroll; }; - // play sounds on water transitions + // play sounds on water transitions + if(!pl->inwater && water) { playsound(S_SPLASH2, &pl->o); pl->vel.z = 0; } else if(pl->inwater && !water) playsound(S_SPLASH1, &pl->o); pl->inwater = water; - + // Added by Rick: Easy hack to store previous locations of all players/monsters/bots + pl->PrevLocations.Update(pl->o); + // End add }; void moveplayer(dynent *pl, int moveres, bool local) diff --git a/source/src/protos.h b/source/src/protos.h index 382fa6cb..d0721b0f 100644 --- a/source/src/protos.h +++ b/source/src/protos.h @@ -17,7 +17,7 @@ extern char *getalias(char *name); // console extern void keypress(int code, bool isdown, int cooked); extern void renderconsole(); -extern void conoutf(const char *s, int a = 0, int b = 0, int c = 0); +// extern void conoutf(const char *s, int a = 0, int b = 0, int c = 0); Moved to cube.h by Rick extern char *getcurcommand(); // menus @@ -84,6 +84,7 @@ extern char *getclientmap(); extern const char *modestr(int n); extern void zapdynent(dynent *&d); extern dynent *getclient(int cn); +extern dynent *getbot(int cn); // Added by Rick extern void timeupdate(int timeremain); extern void resetmovement(dynent *d); extern void fixplayer1range(); @@ -163,7 +164,8 @@ extern void damageblend(int n); // renderparticles extern void setorient(vec &r, vec &u); extern void particle_splash(int type, int num, int fade, vec &p); -extern void particle_trail(int type, int fade, vec &from, vec &to); +//extern void particle_splash(int num, int fade, vec &p); +// extern void particle_trail(int type, int fade, vec &from, vec &to); Moved to cube.h by Rick extern void render_particles(int time); // worldio @@ -191,11 +193,16 @@ extern void playsound(int n, vec *loc = 0); extern void playsoundc(int n); extern void initsound(); extern void cleansound(); +extern void botplaysound(int n, dynent *b); // Added by Rick // rendermd2 extern void rendermodel(char *mdl, int frame, int range, int tex, float rad, float x, float y, float z, float yaw, float pitch, bool teammate, float scale, float speed, int snap = 0, int basetime = 0); extern mapmodelinfo &getmminfo(int i); +// rendermd3 +extern void rendermd3player(dynent *d); +extern void md3setanim(dynent *d, int anim); + // server extern void initserver(bool dedicated, bool listen, int uprate, char *sdesc, char *ip, char *master, char *passwd); extern void cleanupserver(); @@ -224,6 +231,22 @@ extern void moveprojectiles(float time); extern void projreset(); extern char *playerincrosshair(); extern int reloadtime(int gun); +extern int delaytime(int gun); +// Added by Rick +extern char *botincrosshair(); +extern bool intersect(dynent *d, vec &from, vec &to, vec *end = NULL); +// End add by Rick + +/* +// monster +extern void monsterclear(); +extern void restoremonsterstate(); +extern void monsterthink(); +extern void monsterrender(); +extern dvector &getmonsters(); +extern void monsterpain(dynent *m, int damage, dynent *d); +extern void endsp(bool allkilled); +*/ // entities extern void renderents(); @@ -233,8 +256,10 @@ extern void renderentities(); extern void resetspawns(); extern void setspawn(int i, bool on); extern void baseammo(int gun); -extern void checkitems(); -extern void radd(dynent *d); +// Added by Rick +extern void botbaseammo(int gun, dynent *d); +extern bool intersect(entity *e, vec &from, vec &to, vec *end=NULL); +// End add by Rick // rndmap extern void perlinarea(block &b, int scale, int seed, int psize); diff --git a/source/src/rendergl.cpp b/source/src/rendergl.cpp index a5a7786f..6191e1e7 100644 --- a/source/src/rendergl.cpp +++ b/source/src/rendergl.cpp @@ -106,7 +106,7 @@ int texy[MAXTEX]; string texname[MAXTEX]; int curtex = 0; const int FIRSTTEX = 100; // opengl id = loaded id + FIRSTTEX -// std 1+, sky 14+, mdls 20+ +// std 1+, sky 14+, md3s 20+, mdls 30+ const int MAXFRAMES = 2; // increase to allow more complex shader defs int mapping[256][MAXFRAMES]; // ( cube texture, frame ) -> ( opengl id, name ) @@ -370,6 +370,8 @@ void gl_drawframe(int w, int h, float changelod, float curfps) xtraverts = 0; renderclients(); + //monsterrender(); + BotManager.RenderBots(); // Added by Rick renderentities(); @@ -378,6 +380,12 @@ void gl_drawframe(int w, int h, float changelod, float curfps) glDisable(GL_CULL_FACE); + // Added by Rick: Need todo here because of drawing the waypoints + glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); + WaypointClass.Think(); + glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); + // end add + drawhudgun(fovy, aspect, farplane); overbright(1); diff --git a/source/src/rendermd2.cpp b/source/src/rendermd2.cpp index 5e067889..7a3296fb 100644 --- a/source/src/rendermd2.cpp +++ b/source/src/rendermd2.cpp @@ -193,7 +193,7 @@ void md2::render(vec &light, int frame, int range, float x, float y, float z, fl hashtable *mdllookup = NULL; vector mapmodels; -const int FIRSTMDL = 20; +const int FIRSTMDL = 30; void delayedload(md2 *m) { @@ -279,5 +279,6 @@ void rendermodel(char *mdl, int frame, int range, int tex, float rad, float x, f }; */ - m->render(light, frame, range, x, y, z, yaw, pitch, scale, speed, snap, basetime); + m->render(light, frame, range, x, y, z, yaw, pitch, scale, speed, snap, basetime); + //testrender(x, y, z, yaw, pitch, 1.0f); }; diff --git a/source/src/rendermd3.cpp b/source/src/rendermd3.cpp new file mode 100644 index 00000000..6693063e --- /dev/null +++ b/source/src/rendermd3.cpp @@ -0,0 +1,533 @@ +#include "cube.h" + +// loading and rendering code for the MD3 model format +// parts of the code are from gamedev.net + +const int FIRSTMD3 = 20; +int nummodels = 0; +int numskins = 0; + +struct vec2 +{ + float x, y; +}; + +struct md3header +{ + char fileID[4]; + int version; + char filename[68]; + int numFrames, numTags, numMeshes; + int maxSkins; + int headersize; + int tagStart, tagEnd; + int filesize; +}; + +struct md3meshinfo +{ + char meshId[4]; + char name[68]; + int numMeshFrames, numSkins, numVertices, numTriangles; + int triStart; + int headersize; + int uvStart; + int vertexStart; + int meshSize; +}; + +struct md3tag +{ + char name[64]; + vec pos; + float rotation[3][3]; +}; + +struct md3bone +{ + float mins[3], maxs[3]; + float position[3]; + float scale; + char creator[16]; +}; + +struct md3triangle +{ + signed short vertex[3]; + unsigned char normal[2]; +}; + +struct md3face +{ + int vertexindices[3]; +}; + +struct md3texcoord +{ + float texturecoord[2]; +}; + +struct md3skin +{ + char name[68]; +}; + +struct md3tface +{ + int vertindex[3]; + int coordindex[3]; +}; + +struct md3animinfo +{ + int startframe; + int endframe; + bool loop; + int fps; +}; + +struct md3object +{ + int numVerts, numFaces, numTexVertex; + int tex; + char name[255]; + vec *verts; + vec *normals; + vec2 *texVerts; + md3tface *faces; + int numTags;//EDIT: AH +}; + +struct md3model +{ + vector objects; + vector animations; + md3model **links; + md3tag *tags; + int numTags; +/* int curAnim, curFrame, nextFrame; + int lastTime; */ + md3state *animstate; + bool loaded; + int modelnum; + float scale; + bool load(char *mdlpath); + void link(md3model *link, char *tag); + void render(); + void draw(float x, float y, float z, float yaw, float pitch, float rad); + md3model(); + ~md3model(); +}; + +md3model::md3model() +{ + loaded = false; + scale = 0.08f; +}; + +md3model::~md3model() +{ + loopv(objects){ + if(objects[i].verts) delete [] objects[i].verts; + if(objects[i].normals) delete [] objects[i].normals; + if(objects[i].texVerts) delete [] objects[i].texVerts; + if(objects[i].faces) delete [] objects[i].faces; + }; + if(links) free(links); + if(tags) delete [] tags; +}; + +bool md3model::load(char *mdlpath) +{ + loaded = false; + if(!mdlpath) return false; + + FILE *f = fopen(mdlpath, "rb"); + if(!f) { conoutf("could not load md3 model: %s", (int)mdlpath); return false; }; + md3header header; + fread(&header, 1, sizeof(header), f); + char *id = header.fileID; + if(id[0] != 'I' || id[1] != 'D' || id[2] != 'P' || header.version != 15) { // check the header + conoutf("wrong header in md3 model: %s", (int)mdlpath); + return false; + }; + + md3bone *bones = new md3bone[header.numFrames]; // read bones + fread(bones, sizeof(md3bone), header.numFrames, f); + delete [] bones; + + tags = new md3tag[header.numFrames * header.numTags]; // read tags + fread(tags, sizeof(md3tag), header.numFrames * header.numTags, f); + numTags = header.numTags; + + links = (md3model **) malloc(sizeof(md3model) * header.numTags); // alloc mem for the links + loopi(header.numTags) links[i] = NULL; + + long meshOffset = ftell(f); + md3meshinfo meshHeader; + + loopj(header.numMeshes) // go through all sub objects + { + fseek(f, meshOffset, SEEK_SET); // read header + fread(&meshHeader, sizeof(md3meshinfo), 1, f); + + md3skin *skins = new md3skin[meshHeader.numSkins]; + md3texcoord *texcoords = new md3texcoord[meshHeader.numVertices]; + md3face *triangles = new md3face[meshHeader.numTriangles]; + md3triangle *vertices = new md3triangle[meshHeader.numVertices * meshHeader.numMeshFrames]; + + fread(skins, sizeof(md3skin), meshHeader.numSkins, f); // read skin information + + fseek(f, meshOffset + meshHeader.triStart, SEEK_SET); // read triangles/face data + fread(triangles, sizeof(md3face), meshHeader.numTriangles, f); + + fseek(f, meshOffset + meshHeader.uvStart, SEEK_SET); // read UV data + fread(texcoords, sizeof(md3texcoord), meshHeader.numVertices, f); + + fseek(f, meshOffset + meshHeader.vertexStart, SEEK_SET); // read vertex/face index information + fread(vertices, sizeof(md3triangle), meshHeader.numMeshFrames * meshHeader.numVertices, f); + + md3object currentMesh = {0}; + strcpy(currentMesh.name, meshHeader.name); + + currentMesh.numVerts = meshHeader.numVertices; + currentMesh.numTexVertex = meshHeader.numVertices; + currentMesh.numFaces = meshHeader.numTriangles; + + currentMesh.verts = new vec[currentMesh.numVerts * meshHeader.numMeshFrames]; + currentMesh.texVerts = new vec2 [currentMesh.numVerts]; + currentMesh.faces = new md3tface [currentMesh.numFaces]; + + loopi(currentMesh.numVerts * meshHeader.numMeshFrames) + { + currentMesh.verts[i].x = vertices[i].vertex[0] / 64.0f; + currentMesh.verts[i].y = vertices[i].vertex[1] / 64.0f; + currentMesh.verts[i].z = vertices[i].vertex[2] / 64.0f; + } + + loopi(currentMesh.numTexVertex) + { + currentMesh.texVerts[i].x = texcoords[i].texturecoord[0]; + currentMesh.texVerts[i].y = -texcoords[i].texturecoord[1]; + } + + loopi(currentMesh.numFaces) + { + currentMesh.faces[i].vertindex[0] = triangles[i].vertexindices[0]; + currentMesh.faces[i].vertindex[1] = triangles[i].vertexindices[1]; + currentMesh.faces[i].vertindex[2] = triangles[i].vertexindices[2]; + + currentMesh.faces[i].coordindex[0] = triangles[i].vertexindices[0]; + currentMesh.faces[i].coordindex[1] = triangles[i].vertexindices[1]; + currentMesh.faces[i].coordindex[2] = triangles[i].vertexindices[2]; + } + + objects.add(currentMesh); + + delete [] skins; + delete [] texcoords; + delete [] triangles; + delete [] vertices; + meshOffset += meshHeader.meshSize; + }; + + loaded = true; + return true; +}; + +void md3model::link(md3model *link, char *tag) +{ + loopi(numTags) + if(strcmp(tags[i].name, tag) == 0) + { + links[i] = link; + return; + }; +}; + +void modulo(int a, int b) +{ + conoutf("%i", a % b); +}; +COMMAND(modulo, ARG_2INT); + +void md3model::render() +{ + int startfrm = 0; + int endfrm = 1; + float t = 0.0f; + + md3state *a = animstate; + md3animinfo *anim = NULL; + + if(animations.length()) + { + anim = &animations[a->anim]; + if(a->frm < anim->startframe || a->frm > anim->endframe) + { + a->frm = anim->startframe; + a->lastTime = lastmillis; + }; + startfrm = anim->startframe; + endfrm = anim->endframe; + } + + int nextfrm = (a->frm + 1) % endfrm; + if(nextfrm == 0) + { + if(anim && anim->loop) nextfrm = startfrm; + else + a->lastTime = lastmillis; + }; + int elapsedtime = 0; + + if(animations.length()) + { + elapsedtime = lastmillis - a->lastTime; + t = (float) elapsedtime / (1000 / anim->fps); + if(t >= 1.0f) + { + a->frm = nextfrm; + a->lastTime = lastmillis; + }; + }; + + printf("anim:%d\tfrm:%d\tt:%f\n", a->anim, a->frm, t); + + loopv(objects) + { + md3object *obj = &objects[i]; + + int currentIndex = a->frm * obj->numVerts; + int nextIndex = nextfrm * obj->numVerts; + + glBindTexture(GL_TEXTURE_2D, obj->tex + FIRSTMD3); + + glBegin(GL_TRIANGLES); + loopj(obj->numFaces) + { + loop(whichVertex, 3) + { + int index = obj->faces[j].vertindex[whichVertex]; + + if(obj->texVerts) + glTexCoord2f(obj->texVerts[index].x, -obj->texVerts[index].y); + + vec point1 = obj->verts[ currentIndex + index ]; + vec point2 = obj->verts[ nextIndex + index]; + + glVertex3f(point1.x + t * (point2.x - point1.x), + point1.y + t * (point2.y - point1.y), + point1.z + t * (point2.z - point1.z)); + } + } + glEnd(); + } + + #define ip(r, c,n) (r) = (c) + t * ((n) - (c)) + #define ip_vec(c,n) ip(c.x,c.x,n.x); ip(c.y,c.y,n.y); ip(c.z,c.z,n.z); + loopi(numTags) + { + md3model *link = links[i]; + if(!link) continue; + vec pos = tags[a->frm * numTags + i].pos; + vec nextpos = tags[nextfrm * numTags + i].pos; + ip_vec(pos, nextpos); + float rot[3][3]; + loop(j, 3) loop(k, 3) ip(rot[j][k], tags[a->frm * numTags + i].rotation[j][k], tags[nextfrm * numTags + i].rotation[j][k]); + GLfloat matrix[16]; + matrix[0] = rot[0][0]; + matrix[1] = rot[0][1]; + matrix[2] = rot[0][2]; + matrix[3] = 0; + matrix[4] = rot[1][0]; + matrix[5] = rot[1][1]; + matrix[6] = rot[1][2]; + matrix[7] = 0; + matrix[8] = rot[2][0]; + matrix[9] = rot[2][1]; + matrix[10]= rot[2][2]; + matrix[11]= 0; + matrix[12] = pos.x; + matrix[13] = pos.y; + matrix[14] = pos.z; + matrix[15] = 1; + glPushMatrix(); + glMultMatrixf(matrix); + link->render(); + glPopMatrix(); + }; +}; + +void md3model::draw(float x, float y, float z, float yaw, float pitch, float rad) +{ + if(!loaded || objects.length() <= 0) return; + //if(isoccluded(player1->o.x, player1->o.y, x-rad, z-rad, rad*2)) return; + + glPushMatrix(); + + int ix = (int)x; + int iy = (int)z; + vec light = { 1.0f, 1.0f, 1.0f }; + if(!OUTBORD(ix, iy)) + { + sqr *s = S(ix,iy); + float ll = 256.0f; // 0.96f; + float of = 0.0f; // 0.1f; + light.x = 0.75f * s->r/ll+of; + light.y = 0.75f * s->g/ll+of; + light.z = 0.75f * s->b/ll+of; + }; + glColor3fv((float *)&light); + + glTranslatef(x, y, z); // avoid models above ground + + glRotatef(yaw+180, 0, -1, 0); + glRotatef(pitch, 0, 0, 1); + glRotatef(-90, 1, 0, 0); + + glScalef( scale, scale, scale); + glTranslatef( 0.0f, 0.0f, 24.0f ); + + render(); + + glPopMatrix(); +}; + +void md3setanim(dynent *d, int anim) +{ + if(anim <= BOTH_DEAD3) // assign to both, legs and torso + { + d->animstate[MDL_LOWER].anim = anim; + d->animstate[MDL_UPPER].anim = anim; + } + else if(anim <= TORSO_STAND2) + d->animstate[MDL_UPPER].anim = anim; + else + d->animstate[MDL_LOWER].anim = anim - 7; +}; + +vector playermodels; +vector weaponmodels; +vector tmp_animations; +char basedir[_MAXDEFSTR]; // necessary for relative path's + +void md3skin(char *objname, char *skin) // called by the {lower|upper|head}.cfg (originally .skin in Q3A) +{ + md3model *mdl = playermodels.last(); + loopv(mdl->objects) + { + md3object *obj = &mdl->objects[i]; + if(strcmp(obj->name, objname) == 0) + { + int xs, ys; + sprintf_sd(path)("%s/%s", basedir, skin); // 'skin' is a relative url + installtex(FIRSTMD3 + numskins, path, xs, ys); + obj->tex = numskins; + numskins++; + }; + }; +}; + +COMMAND(md3skin, ARG_2STR); + +void md3animation(char *firstframe, char *numframes, char *loopingframes, char *fps) /* configurable animations - use hardcoded instead ? */ +{ + md3animinfo &a = tmp_animations.add(); + a.startframe = atoi(firstframe); + a.endframe = a.startframe + atoi(numframes); + (atoi(loopingframes) > 0) ? a.loop = true : a.loop = false; + a.fps = atoi(fps); +}; + +COMMAND(md3animation, ARG_5STR); + +/* +void rendermd3(char *mdl, float x, float y, float z, float yaw, float pitch, float rad) +{ + if(playermodels.length()) playermodels[MDL_LOWER]->draw(x, y, z, yaw, pitch, rad); +};*/ + +VAR(anim, 0, 0, 1000); + +void rendermd3player(dynent *d) +{ + if(playermodels.length() >= 3 && d->mdl[MDL_LOWER] == 0) + { + loopi(3) playermodels[d->mdl[i]]->animstate = &d->animstate[i]; // get d's current frame + + md3setanim(d, anim); + + int n; + float mz = d->o.z-d->eyeheight+1.55f; + /*if(d->state==CS_DEAD) + { + + md3setanim(d, BOTH_DEATH1 + rnd(2) * 2); + //mdl = (((int)d>>6)&1)+1; + //mz = d->o.z-d->eyeheight+0.2f; + //scale = 1.2f; + } + else if(d->state==CS_EDITING) { md3setanim(d, TORSO_STAND); } + else if(d->state==CS_LAGGED) { md3setanim(d, LEGS_IDLE); md3setanim(d, TORSO_GESTURE); } + else if(d->monsterstate==M_ATTACKING) { n = 8; } + else if(d->monsterstate==M_PAIN) { n = 10; } + else if((!d->move && !d->strafe) || !d->moving) { md3setanim(d, LEGS_IDLE); md3setanim(d, TORSO_STAND); } + else if(!d->onfloor && d->timeinair>100) { md3setanim(d, LEGS_JUMP ); } + else { n = 14; }; */ + + playermodels[d->mdl[MDL_LOWER]]->draw(d->o.x, mz, d->o.y, d->yaw+90, d->pitch, d->radius); + }; +}; + +void testrender(float x, float y, float z, float yaw, float pitch, float radius) +{ + if(playermodels.length()) + { + playermodels[0]->draw(x, z, y, yaw+90, pitch, radius); + }; +}; + +md3model *loadmd3(char *path) // loads a single md3 model and the skins for its objects (cfg file) +{ + md3model *mdl = new md3model(); + playermodels.add(mdl); + sprintf_sd(mdl_path)("%s.md3", path); + sprintf_sd(cfg_path)("%s.cfg", path); + playermodels.last()->load(mdl_path); + exec(cfg_path); + return playermodels.last(); +}; + +char pl_objects[3][6] = {"lower", "upper", "head"}; + +void loadplayermdl(char *model) +{ + tmp_animations.setsize(0); + sprintf(basedir, "packages/models/playermodels/%s", model); + md3model *mdls[3]; + loopi(3) // load lower,upper and head objects + { + sprintf_sd(path)("%s/%s", basedir, pl_objects[i]); + mdls[i] = loadmd3(path); + }; + mdls[MDL_LOWER]->link(mdls[MDL_UPPER], "tag_torso"); + mdls[MDL_UPPER]->link(mdls[MDL_HEAD], "tag_head"); + sprintf_sd(modelcfg)("%s/model.cfg", basedir); + exec(modelcfg); // load animations to tmp_animation + loopv(tmp_animations) // assign the loaded animations to the lower,upper or head model + { + md3animinfo &anim = tmp_animations[i]; + if(i <= BOTH_DEAD3) + { + mdls[MDL_UPPER]->animations.add(anim); + mdls[MDL_LOWER]->animations.add(anim); + } + else if(i <= TORSO_STAND2) + mdls[MDL_UPPER]->animations.add(anim); + else if(i <= LEGS_TURN) + mdls[MDL_LOWER]->animations.add(anim); + }; +}; + +COMMAND(loadplayermdl, ARG_1STR); diff --git a/source/src/savegamedemo.cpp b/source/src/savegamedemo.cpp index 0421a828..ac9f6663 100644 --- a/source/src/savegamedemo.cpp +++ b/source/src/savegamedemo.cpp @@ -35,6 +35,15 @@ void stop() demoloading = false; loopv(playerhistory) zapdynent(playerhistory[i]); playerhistory.setsize(0); + // Added by Rick: Remove bots + loopv(bots) + { + if (!bots[i]) continue; + delete bots[i]->pBot; + bots[i]->pBot = NULL; + zapdynent(bots[i]); + } + bots.setsize(0); }; void stopifrecording() { if(demorecording) stop(); }; @@ -105,17 +114,33 @@ void loadgamerest() { if(demoplayback || !f) return; + //Modified by Rick, MSVC 6.0 doesnt seem to like this + //if(gzgeti()!=ents.length()) return loadgameout(); if(gzgeti()!=ents.length()) { loadgameout(); return; } loopv(ents) { ents[i].spawned = gzgetc(f)!=0; - if(ents[i].type==CARROT && !ents[i].spawned) trigger(ents[i].attr1, ents[i].attr2, true); + if(ents[i].type==TRIGGER && !ents[i].spawned) trigger(ents[i].attr1, ents[i].attr2, true); }; restoreserverstate(ents); gzread(f, player1, sizeof(dynent)); player1->lastaction = lastmillis; + int nmonsters = gzgeti(); + //dvector &monsters = getmonsters(); + //Modified by Rick, MSVC 6.0 doesnt seem to like this + //if(nmonsters!=monsters.length()) return loadgameout(); + //if(nmonsters!=monsters.length()) { loadgameout(); return; } + //loopv(monsters) + //{ + //gzread(f, monsters[i], sizeof(dynent)); + //monsters[i]->enemy = player1; // lazy, could save id of enemy instead + //monsters[i]->lastaction = monsters[i]->trigger = lastmillis+500; // also lazy, but no real noticable effect on game + //if(monsters[i]->state==CS_DEAD) monsters[i]->lastaction = 0; + //}; + //restoremonsterstate(); + int nplayers = gzgeti(); loopi(nplayers) if(!gzget()) { diff --git a/source/src/server.cpp b/source/src/server.cpp index a3406004..6e98ecfe 100644 --- a/source/src/server.cpp +++ b/source/src/server.cpp @@ -13,23 +13,31 @@ struct client // server side version of "dynent" type string mapvote; string name; int modevote; + // Added by Rick: For bot voting + bool addbot, kickbot, changebotskill; + short botcount; + string botteam, botname; + short botskill; + bool kickallbots; + // End add }; vector clients; string smapname; -struct server_entity // server side version of "entity" type -{ - bool spawned; - int spawnsecs; -}; - vector sents; bool notgotitems = true; // true when map has changed and waiting for clients to send item int mode = 0; +// Added by Rick +#ifndef STANDALONE +extern void kickbot(const char *szName); +extern void kickallbots(void); +#endif +// End add + void restoreserverstate(vector &ents) // hack: called from savegame code, only works in SP { loopv(sents) @@ -145,6 +153,206 @@ bool vote(char *map, int reqmode, int sender) return true; }; +// Added by Rick: Vote for bot commands +void resetbotvotes() +{ + loopv(clients) + { + clients[i].addbot = false; + clients[i].kickbot = false; + clients[i].changebotskill = false; + clients[i].kickallbots = false; + clients[i].botcount = 0; + clients[i].botteam[0] = 0; + clients[i].botname[0] = 0; + clients[i].botskill = -1; + } +}; + +bool addbotvote(int count, char *team, int skill, char *name, int sender) +{ +#ifndef STANDALONE + clients[sender].addbot = true; + clients[sender].kickbot = false; + clients[sender].changebotskill = false; + clients[sender].botcount = (count>=0) ? count : 0; + if (team && team[0]) strcpy_s(clients[sender].botteam, team); + else clients[sender].botteam[0] = 0; + clients[sender].botskill = skill; + if (name && name[0]) strcpy_s(clients[sender].botname, name); + else clients[sender].botname[0] = 0; + int yes = 0, no = 0; + loopv(clients) if(clients[i].type!=ST_EMPTY) + { + if (clients[i].addbot) + { + if (!strcmp(clients[i].botteam, team) && !strcmp(clients[i].botname, name) && + (clients[i].botskill == skill) && + (clients[i].botcount == clients[sender].botcount)) + yes++; + else + no++; + } + else no++; + } + if(yes==1 && no==0) return true; // single player + sprintf_sd(msg)("%s suggests to add a bot", clients[sender].name); + if (team && team[0]) + { + strcat(msg, " on team "); + strcat(msg, team); + } + + if (skill!=-1) + { + strcat(msg, ", with skill "); + strcat(msg, SkillNrToSkillName(skill)); + } + + if (name && name[0]) + { + strcat(msg, " named "); + strcat(msg, name); + } + + sendservmsg(msg); + if(yes/(float)(yes+no) <= 0.5f) return false; + sendservmsg("vote passed"); + resetbotvotes(); +#endif + return true; +}; + +bool kickbotvote(int specific, char *name, int sender) +{ + clients[sender].addbot = false; + clients[sender].kickbot = true; + clients[sender].changebotskill = false; + clients[sender].kickallbots = !specific; + if (name && name[0]) strcpy_s(clients[sender].botname, name); + else clients[sender].botname[0] = 0; + int yes = 0, no = 0; + loopv(clients) if(clients[i].type!=ST_EMPTY) + { + if (clients[i].kickbot) + { + if (clients[sender].kickallbots) + { + if (clients[i].kickallbots) yes++; + else no++; + } + else + { + if (!clients[i].kickallbots && !strcmp(clients[i].botname, name)) + yes++; + else + no++; + } + } + else no++; + } + if(yes==1 && no==0) return true; // single player + + char msg[256]; + if (clients[sender].kickallbots) + sprintf(msg, "%s suggests to kick all bots", clients[sender].name); + else + sprintf(msg, "%s suggests to kick bot %s", clients[sender].name, + clients[sender].botname); + + sendservmsg(msg); + if(yes/(float)(yes+no) <= 0.5f) return false; + sendservmsg("vote passed"); + resetbotvotes(); + return true; +}; + +bool botskillvote(int skill, int sender) +{ +#ifndef STANDALONE + clients[sender].addbot = false; + clients[sender].kickbot = false; + clients[sender].changebotskill = true; + clients[sender].botskill = skill; + int yes = 0, no = 0; + loopv(clients) if(clients[i].type!=ST_EMPTY) + { + if (clients[i].changebotskill) + { + if (clients[sender].botskill == clients[i].botskill) yes++; + else no++; + } + else no++; + } + if(yes==1 && no==0) return true; // single player + + sprintf_sd(msg)("%s suggests to change the skill of all bots to: %s", clients[sender].name, + SkillNrToSkillName(clients[sender].botskill)); + sendservmsg(msg); + if(yes/(float)(yes+no) <= 0.5f) return false; + sendservmsg("vote passed"); + resetbotvotes(); +#endif + return true; +}; + +void botcommand(uchar *&p, char *text, int sender) +{ + int type = getint(p); + switch(EBotCommands(type)) + { + case COMMAND_ADDBOT: + { + string name, team; + int count = getint(p); + int skill = getint(p); + sgetstr(); + strcpy(team, text); + sgetstr(); + strcpy(name, text); +#ifndef STANDALONE + if (addbotvote(count, team, skill, name, sender)) + { + while(count>0) + { + BotManager.CreateBot(team, SkillNrToSkillName(skill), name); + count--; + } + } +#endif + break; + } + case COMMAND_KICKBOT: + { + int specific = getint(p); + + if (specific) + sgetstr(); +#ifndef STANDALONE + if (kickbotvote(specific, text, sender)) + { + if (specific) + kickbot(text); + else + kickallbots(); + } +#endif + } + case COMMAND_BOTSKILL: + { + int skill = getint(p); +#ifndef STANDALONE + if (botskillvote(skill, sender)) + { + BotManager.ChangeBotSkill(skill, NULL); + } +#endif + } + } +} + +// End add + // server side processing of updates: does very little and most state is tracked client only // could be extended to move more gameplay to server (at expense of lag) @@ -207,7 +415,13 @@ void process(ENetPacket * packet, int sender) // sender may be -1 case SV_ITEMPICKUP: { int n = getint(p); - pickup(n, getint(p), sender); + // pickup(n, getint(p), sender); Modified by Rick + int spawnsec = getint(p); + + if (getint(p)) // Only call pickup() if this isn't a bot + pickup(n, spawnsec, sender); + // End mod + break; }; @@ -241,6 +455,21 @@ void process(ENetPacket * packet, int sender) // sender may be -1 send(sender, recvmap(sender)); return; + // Added by Rick: Bot specifc messages + case SV_ADDBOT: + getint(p); + sgetstr(); + //strcpy_s(clients[cn].name, text); + sgetstr(); + getint(p); + break; + case SV_BOTCOMMAND: // Client asked server for a bot command + { + botcommand(p, text, sender); + break; + } + // End add by Rick + case SV_EXT: // allows for new features that require no server updates { for(int n = getint(p); n; n--) getint(p); @@ -322,6 +551,7 @@ void resetserverifempty() clients.setsize(0); smapname[0] = 0; resetvotes(); + resetbotvotes(); // Added by Rick resetitems(); mode = 0; mapreload = false; @@ -404,6 +634,9 @@ void serverslice(int seconds, unsigned int timeout) // main server update, cal send2(true, -1, SV_CDIS, (int)event.peer->data); event.peer->data = (void *)-1; break; + + default: + break; }; }; diff --git a/source/src/serverutil.cpp b/source/src/serverutil.cpp index 34e6c2ee..a378566c 100644 --- a/source/src/serverutil.cpp +++ b/source/src/serverutil.cpp @@ -35,11 +35,11 @@ const char *modenames[] = //const char *modestr(int n) { return (n>=-2 && n<12) ? modenames[n+2] : "unknown"; }; const char *modestr(int n) { return (n>=0 && n<7) ? modenames[n] : "unknown"; }; - +/* Modified by Rick: New messages and different sizes for existing ones char msgsizesl[] = // size inclusive message token, 0 for variable or not-checked sizes { SV_INITS2C, 4, SV_INITC2S, 0, SV_POS, 12, SV_TEXT, 0, SV_SOUND, 2, SV_CDIS, 2, - SV_DIED, 2, SV_DAMAGE, 4, SV_SHOT, 8, SV_FRAGS, 2, + SV_DIED, 2, SV_DAMAGE, 4, SV_SHOsT, 8, SV_FRAGS, 2, SV_TIMEUP, 2, SV_EDITENT, 10, SV_MAPRELOAD, 2, SV_ITEMACC, 2, SV_MAPCHANGE, 0, SV_ITEMSPAWN, 2, SV_ITEMPICKUP, 3, SV_DENIED, 2, SV_PING, 2, SV_PONG, 2, SV_CLIENTPING, 2, SV_GAMEMODE, 2, @@ -48,6 +48,24 @@ char msgsizesl[] = // size inclusive message token, 0 for variable SV_EXT, 0, -1 }; +*/ + +char msgsizesl[] = // size inclusive message token, 0 for variable or not-checked sizes +{ + SV_INITS2C, 4, SV_INITC2S, 0, SV_POS, 12, SV_TEXT, 0, SV_SOUND, 2, SV_CDIS, 2, + SV_DIED, 3, SV_DAMAGE, 6, SV_SHOT, 9, SV_FRAGS, 2, + SV_TIMEUP, 2, SV_EDITENT, 10, SV_MAPRELOAD, 2, SV_ITEMACC, 2, + SV_MAPCHANGE, 0, SV_ITEMSPAWN, 2, SV_ITEMPICKUP, 4, SV_DENIED, 2, + SV_PING, 2, SV_PONG, 2, SV_CLIENTPING, 2, SV_GAMEMODE, 2, + SV_EDITH, 7, SV_EDITT, 7, SV_EDITS, 6, SV_EDITD, 6, SV_EDITE, 6, + SV_SENDMAP, 0, SV_RECVMAP, 1, SV_SERVMSG, 0, SV_ITEMLIST, 0, + // Added by Rick: Bot specific messages + SV_BOTSOUND, 3, SV_BOTDIS, 2, SV_BOTDIED, 4, SV_CLIENT2BOTDMG, 4, SV_BOT2BOTDMG, 2, + SV_BOTFRAGS, 3, SV_ADDBOT, 5, SV_BOTUPDATE, 12, SV_BOTCOMMAND, 0, + // End add + SV_EXT, 0, + -1 +}; char msgsizelookup(int msg) { diff --git a/source/src/sound.cpp b/source/src/sound.cpp index 26ec40cf..35fb118d 100644 --- a/source/src/sound.cpp +++ b/source/src/sound.cpp @@ -125,6 +125,13 @@ COMMAND(registersound, ARG_1EST); void playsoundc(int n) { addmsg(0, 2, SV_SOUND, n); playsound(n); }; +// Added by Rick: For bots +void botplaysound(int n, dynent *b) +{ + addmsg(0, 3, SV_BOTSOUND, n, BotManager.GetBotIndex(b)); playsound(n, &b->o); +} +// End add by Rick + int soundsatonce = 0, lastsoundmillis = 0; void playsound(int n, vec *loc) diff --git a/source/src/weapon.cpp b/source/src/weapon.cpp index 6f670bb9..ec76f825 100644 --- a/source/src/weapon.cpp +++ b/source/src/weapon.cpp @@ -21,6 +21,7 @@ guninfo guns[NUMGUNS] = }; + //weapon selection void next() { @@ -104,7 +105,7 @@ void reload() COMMAND(reload,ARG_NONE); int reloadtime(int gun) { return guns[gun].attackdelay; }; - +int delaytime(int gun) { return guns[gun].attackdelay; }; void createrays(vec &from, vec &to) // create random spread of rays for the shotgun { @@ -119,7 +120,8 @@ void createrays(vec &from, vec &to) // create random spread of rays }; }; -bool intersect(dynent *d, vec &from, vec &to) // if lineseg hits entity bounding box +//bool intersect(dynent *d, vec &from, vec &to) // if lineseg hits entity bounding box +bool intersect(dynent *d, vec &from, vec &to, vec *end) // if lineseg hits entity bounding box { vec v = to, w = d->o, *p; vsub(v, from); @@ -140,12 +142,25 @@ bool intersect(dynent *d, vec &from, vec &to) // if lineseg hits entity boundi }; }; + /* Modified by Rick return p->x <= d->o.x+d->radius && p->x >= d->o.x-d->radius && p->y <= d->o.y+d->radius && p->y >= d->o.y-d->radius && p->z <= d->o.z+d->aboveeye && p->z >= d->o.z-d->eyeheight; + */ + if( p->x <= d->o.x+d->radius + && p->x >= d->o.x-d->radius + && p->y <= d->o.y+d->radius + && p->y >= d->o.y-d->radius + && p->z <= d->o.z+d->aboveeye + && p->z >= d->o.z-d->eyeheight) + { + if (end) *end = *p; + return true; + } + return false; }; char *playerincrosshair() @@ -160,6 +175,20 @@ char *playerincrosshair() return NULL; }; +// Added by Rick +char *botincrosshair() +{ + if(demoplayback) return NULL; + loopv(bots) + { + dynent *o = bots[i]; + if(!o) continue; + if(intersect(o, player1->o, worldpos)) return o->name; + }; + return NULL; +}; +// End add by Rick + const int MAXPROJ = 100; struct projectile { vec o, to; float speed; dynent *owner; int gun; bool inuse, local; }; projectile projs[MAXPROJ]; @@ -186,9 +215,59 @@ void newprojectile(vec &from, vec &to, float speed, bool local, dynent *owner, i void hit(int target, int damage, dynent *d, dynent *at) { if(d==player1) selfdamage(damage, at==player1 ? -1 : -2, at); - else { addmsg(1, 4, SV_DAMAGE, target, damage, d->lifesequence); playsound(S_PAIN1+rnd(5), &d->o); }; + //else if(d->monsterstate) monsterpain(d, damage, at); + // Added by Rick: Let bots take damage + else if (d->pBot) d->pBot->BotPain(damage, at); + else if (d->bIsBot) + { + int PlayerIndex = -1; + if (at->bIsBot) PlayerIndex = BotManager.GetBotIndex(at); + else + { + loopv(players) + { + if (!players[i]) continue; + + if (players[i] == at) + { + PlayerIndex = i; + break; + } + } + } + + int msgtype = (at->bIsBot) ? SV_BOT2BOTDMG : SV_CLIENT2BOTDMG; + addmsg(1, 4, msgtype, BotManager.GetBotIndex(d), damage, PlayerIndex); + playsound(S_PAIN1+rnd(5), &d->o); + } + // End add by Rick + //else { addmsg(1, 4, SV_DAMAGE, target, damage, d->lifesequence); playsound(S_PAIN1+rnd(5), &d->o); }; Modified by Rick: Added IsBot and PlayerIndex to message + else + { + // Modified by Rick: Added IsBot and PlayerIndex to message + int PlayerIndex = -1; + if (at->bIsBot) + PlayerIndex = BotManager.GetBotIndex(at); + else + { + loopv(players) + { + if (!players[i]) + continue; + + if (players[i] == at) + { + PlayerIndex = i; + break; + } + } + } + addmsg(1, 6, SV_DAMAGE, target, damage, d->lifesequence, at->bIsBot, PlayerIndex); + playsound(S_PAIN1+rnd(5), &d->o); + }; + particle_splash(3, damage, 1000, d->o); - demodamage(damage, d->o); + demodamage(damage, d->o); }; const float RL_RADIUS = 5; @@ -209,11 +288,13 @@ void radialeffect(dynent *o, vec &v, int cn, int qdam, dynent *at) }; }; -void splash(projectile *p, vec &v, vec &vold, int notthisplayer, int notthismonster, int qdam) +// Modified by Rick: Added notthisbot +void splash(projectile *p, vec &v, vec &vold, int notthisplayer, int notthismonster, int notthisbot, int qdam) { particle_splash(0, 50, 300, v); p->inuse = false; if(p->gun!=GUN_GRENADE) + if(p->gun!=GUN_GRENADE) //changed { playsound(S_FEXPLODE, &v); // no push? @@ -232,15 +313,29 @@ void splash(projectile *p, vec &v, vec &vold, int notthisplayer, int notthismons if(!o) continue; radialeffect(o, v, i, qdam, p->owner); }; - }; + // Added by Rick + loopv(bots) + { + if(i==notthisbot) continue; + dynent *o = bots[i]; + if(!o) continue; + radialeffect(o, v, i, qdam, p->owner); + } + // End add by Rick + //dvector &mv = getmonsters(); + //loopv(mv) if(i!=notthismonster) radialeffect(mv[i], v, i, qdam, p->owner); + }; }; -inline void projdamage(dynent *o, projectile *p, vec &v, int i, int im, int qdam) +// Modified by Rick +// inline void projdamage(dynent *o, projectile *p, vec &v, int i, int im, int qdam) +inline void projdamage(dynent *o, projectile *p, vec &v, int i, int im, int ib, int qdam) { if(o->state!=CS_ALIVE) return; if(intersect(o, p->o, v)) { - splash(p, v, p->o, i, im, qdam); + // splash(p, v, p->o, i, im, qdam); Modified by Rick + splash(p, v, p->o, i, im, ib, qdam); hit(i, qdam, o, p->owner); }; }; @@ -263,17 +358,36 @@ void moveprojectiles(float time) { dynent *o = players[i]; if(!o) continue; - projdamage(o, p, v, i, -1, qdam); + // projdamage(o, p, v, i, -1, qdam); Modified by Rick + projdamage(o, p, v, i, -1, -1, qdam); }; - if(p->owner!=player1) projdamage(player1, p, v, -1, -1, qdam); + // Added by Rick: Damage bots aswell + loopv(bots) + { + dynent *o = bots[i]; + if(!o || (o == p->owner)) continue; + projdamage(o, p, v, -1, -1, i, qdam); + }; + // End add by Rick + // if(p->owner!=player1) projdamage(player1, p, v, -1, -1, qdam); Modified by Rick + if(p->owner!=player1) projdamage(player1, p, v, -1, -1, -1, qdam); + //dvector &mv = getmonsters(); + //loopv(mv) if(!vreject(mv[i]->o, v, 10.0f) && mv[i]!=p->owner) projdamage(mv[i], p, v, -1, i, qdam); Modified by Rick + //loopv(mv) if(!vreject(mv[i]->o, v, 10.0f) && mv[i]!=p->owner) projdamage(mv[i], p, v, -1, i, -1, qdam); }; if(p->inuse) { - if(time==dtime) splash(p, v, p->o, -1, -1, qdam); + // if(time==dtime) splash(p, v, p->o, -1, -1, qdam); Modified by Rick + if(time==dtime) splash(p, v, p->o, -1, -1, -1, qdam); else { if(p->gun==GUN_GRENADE) { dodynlight(p->o, v, 0, 255, p->owner); particle_splash(5, 2, 200, v); } else { particle_splash(1, 1, 200, v); particle_splash(guns[p->gun].part, 1, 1, v); }; + // Added by Rick + traceresult_s tr; + TraceLine(p->o, v, p->owner, true, &tr); + if (tr.collided) splash(p, v, p->o, -1, -1, -1, qdam); + // End add }; }; p->o = v; @@ -364,7 +478,6 @@ void spreadandrecoil(vec & from, vec & to, dynent * d) if (d->gunselect!=GUN_SHOTGUN) //no spread on shotgun { - #define RNDD (rnd(spd)-spd/2)*f vec r = { RNDD, RNDD, RNDD }; vadd(to, r); }; @@ -395,6 +508,24 @@ void shoot(dynent *d, vec &targ) }; d->lastaction = lastmillis; d->lastattackgun = d->gunselect; + // Modified by Rick + // if(!d->mag[d->gunselect]) { playsoundc(S_NOAMMO); d->gunwait = 250; d->lastattackgun = -1; return; }; + + if(!d->mag[d->gunselect]) + { + if (d->bIsBot) botplaysound(S_NOAMMO, d); + else playsoundc(S_NOAMMO); + d->gunwait = 250; + d->lastattackgun = -1; + return; + }; + // End mod + + if (d->gunselect!=0) //if knife skip + { + if(d->gunselect) d->mag[d->gunselect]--; + } + if(!d->mag[d->gunselect]) { playsoundc(S_NOAMMO); d->gunwait = 250; d->lastattackgun = -1; return; }; if(d->gunselect) d->mag[d->gunselect]--; vec from = d->o; @@ -414,10 +545,25 @@ void shoot(dynent *d, vec &targ) }; if(d->gunselect==GUN_SHOTGUN) createrays(from, to); - if(d->quadmillis && attacktime>200) playsoundc(S_ITEMPUP); + //if(d->quadmillis && attacktime>200) playsoundc(S_ITEMPUP); Modified by Rick + if(d->quadmillis && attacktime>200) + { + if (d->bIsBot) botplaysound(S_ITEMPUP, d); + else playsoundc(S_ITEMPUP); + } + // End mod shootv(d->gunselect, from, to, d, true); - addmsg(1, 8, SV_SHOT, d->gunselect, (int)(from.x*DMF), (int)(from.y*DMF), (int)(from.z*DMF), (int)(to.x*DMF), (int)(to.y*DMF), (int)(to.z*DMF)); - d->gunwait = guns[d->gunselect].attackdelay; + // Modified by Rick + // if(!d->monsterstate) addmsg(1, 8, SV_SHOT, d->gunselect, (int)(from.x*DMF), (int)(from.y*DMF), (int)(from.z*DMF), (int)(to.x*DMF), (int)(to.y*DMF), (int)(to.z*DMF)); + if(!d->monsterstate) + { + int index = -1; + if (d->bIsBot) + index = BotManager.GetBotIndex(d); + addmsg(1, 9, SV_SHOT, d->gunselect, (int)(from.x*DMF), (int)(from.y*DMF), (int)(from.z*DMF), (int)(to.x*DMF), (int)(to.y*DMF), (int)(to.z*DMF), index); + } + // End mod + d->gunwait = delaytime(d->gunselect);// hack guns[d->gunselect].attackdelay; if(guns[d->gunselect].projspeed) return; @@ -428,6 +574,97 @@ void shoot(dynent *d, vec &targ) raydamage(o, from, to, d, i); }; + // Added by Rick: raydamage on bots too + loopv(bots) + { + dynent *o = bots[i]; + if(!o || (o == d)) continue; + raydamage(o, from, to, d, i); + }; + // End add by Rick + + //dvector &v = getmonsters(); + //loopv(v) if(v[i]!=d) raydamage(v[i], from, to, d, -2); + + //if(d->monsterstate) raydamage(player1, from, to, d, -1); Modified by Rick + if(d->monsterstate || d->bIsBot) raydamage(player1, from, to, d, -1); +}; + +//currently client side only, need to make serv +void additem(int gun) +{ + if (gun>0 && gun<10) + { +// player1->mag[gun] = guns[gun].magazine; +// player1->ammo[gun] = guns[gun].magazine * 3; + + }; + + if (gun==10) //helmet + { + conoutf("NO HELMET FOR YOU!"); +// player1->armour = 20 +// break; + }; + + if (gun==11) //armour + { + conoutf("you can't add armor yet!"); +// additem(I_ARMOUR, player1->armour, 20); +// break; + }; +}; + +COMMAND(additem,ARG_1INT); + + +//gotta find a neater way of doing this -argh +void altattack(bool on) +{ +/* + if (on) + { + switch(player1->gunselect) + { + case(GUN_KNIFE): + break; + case(GUN_SEMIPISTOL): + break; + case(GUN_SHOTGUN): + break; + case(GUN_SNIPER): + setvar("fov",getvar("fov")-55); + break; + case(GUN_SUBGUN): + break; + case(GUN_AUTORIFLE): + break; + case(GUN_GRENADE): + break; + }; + } + else + { + switch(player1->gunselect) + { + case(GUN_KNIFE): + break; + case(GUN_SEMIPISTOL): + break; + case(GUN_SHOTGUN): + break; + case(GUN_SNIPER): + setvar("fov",getvar("fov")+55); + break; + case(GUN_SUBGUN): + break; + case(GUN_AUTORIFLE): + break; + case(GUN_GRENADE): + break; + }; + }; + */ }; diff --git a/source/src/world.cpp b/source/src/world.cpp index d5d7a338..a4f60def 100644 --- a/source/src/world.cpp +++ b/source/src/world.cpp @@ -37,7 +37,16 @@ void settag(int tag, int type) // set all cubes with "tag" to space, if }; void resettagareas() { settag(0, 0); }; // reset for editing or map saving -void settagareas() { settag(0, 1); loopv(ents) if(ents[i].type==CARROT) setspawn(i, true); }; // set for playing +// Modified by Rick +//void settagareas() { settag(0, 1); loopv(ents) if(ents[i].type==TRIGGER) setspawn(i, true); }; // set for playing + +void settagareas() // set for playing +{ + settag(0, 1); + loopv(ents) if(ents[i].type==TRIGGER) setspawn(i, true); + if(ishost()) BotManager.PickNextTrigger(); +}; +// End mod void trigger(int tag, int type, bool savegame) {