diff --git a/src/bot.c b/src/bot.c new file mode 100644 index 0000000..c16d6a3 --- /dev/null +++ b/src/bot.c @@ -0,0 +1,299 @@ +#include "g_local.h" +#include "m_player.h" +#include "bot.h" + +void droptofloor (edict_t *ent); + +edict_t *bot_team_flag1; +edict_t *bot_team_flag2; + +void SetBotFlag1(edict_t *ent) //チーム1の旗 +{ + bot_team_flag1 = ent; +} +void SetBotFlag2(edict_t *ent) //チーム2の旗 +{ + bot_team_flag2 = ent; +} +edict_t *GetBotFlag1() //チーム1の旗 +{ + return bot_team_flag1; +} +edict_t *GetBotFlag2() //チーム2の旗 +{ + return bot_team_flag2; +} + +qboolean ChkTFlg( void ) +{ + if(bot_team_flag1 != NULL + && bot_team_flag2 != NULL) return true; + else return false; +} + +void SpawnItem2 (edict_t *ent, gitem_t *item) +{ +// PrecacheItem (item); + + ent->item = item; + ent->nextthink = level.time ; // items start after other solids + ent->think = droptofloor; + ent->s.effects = 0; + ent->s.renderfx = RF_GLOW; +// if (ent->model) +// gi.modelindex (ent->model); + droptofloor (ent); + ent->s.modelindex = 0; + ent->nextthink = level.time + 100 * FRAMETIME; // items start after other solids + ent->think = G_FreeEdict; +} + +//===================================== + +// +// BOT用可視判定 +// + +qboolean Bot_trace (edict_t *ent,edict_t *other) +{ + trace_t rs_trace; + vec3_t ttx; + vec3_t tty; + + VectorCopy (ent->s.origin,ttx); + VectorCopy (other->s.origin,tty); + if(ent->maxs[2] >=32) + { + if(tty[2] > ttx[2] ) tty[2] += 16; +// else if(ttx[2] > tty[2] > 100 ) tty[2] += 32; + ttx[2] += 30; + } + else + { + ttx[2] -= 12; + } + + rs_trace = gi.trace (ttx, NULL, NULL, tty ,ent, CONTENTS_SOLID | CONTENTS_WINDOW | CONTENTS_LAVA | CONTENTS_SLIME /*| CONTENTS_TRANSLUCENT*/); + if(rs_trace.fraction == 1.0 && !rs_trace.allsolid && !rs_trace.startsolid) return true; + if( ent->maxs[2] < 32 ) return false; + + if(other->classname[6] == 'F' + || other->classname[0] == 'w') + {} + else if(other->classname[0]=='i') + { + if(other->classname[5]=='q' + || other->classname[5]=='f' + || other->classname[5]=='t' + || other->classname[5]=='i' + || other->classname[5]=='h' + || other->classname[5]=='a'){} + else return false; + } + else return false; + + if(rs_trace.ent != NULL) + { + if(rs_trace.ent->classname[0] == 'f' + && rs_trace.ent->classname[5] == 'd' + && rs_trace.ent->targetname == NULL) return true; + } + + if(ent->s.origin[2] < other->s.origin[2] + || ent->s.origin[2]-24 > other->s.origin[2]) return false; + + ttx[2] -= 36; + rs_trace = gi.trace (ttx, NULL, NULL, other->s.origin ,ent, CONTENTS_SOLID | CONTENTS_WINDOW | CONTENTS_LAVA | CONTENTS_SLIME /*|CONTENTS_TRANSLUCENT*/); + if(rs_trace.fraction == 1.0 && !rs_trace.allsolid && !rs_trace.startsolid) return true; + return false; +} + + +qboolean Bot_traceX (edict_t *ent,edict_t *other) +{ + trace_t rs_trace; + vec3_t ttx,tty; + VectorCopy (ent->s.origin,ttx); + VectorCopy (other->s.origin,tty); + ttx[2] += 16; + tty[2] += 16; + + rs_trace = gi.trace (ttx, NULL, NULL, tty ,ent, CONTENTS_SOLID | CONTENTS_WINDOW | CONTENTS_LAVA | CONTENTS_SLIME); + if(rs_trace.fraction == 1.0 ) return true; + return false; +} + +qboolean Bot_traceY (edict_t *ent,edict_t *other) +{ + trace_t rs_trace; + vec3_t ttx,tty; + VectorCopy (ent->s.origin,ttx); + VectorCopy (other->s.origin,tty); + if(ent->maxs[2] >=32) ttx[2] += 24; + else ttx[2] -= 12; + + tty[2] += 16; + + rs_trace = gi.trace (ttx, NULL, NULL, tty ,ent, CONTENTS_SOLID | CONTENTS_WINDOW | CONTENTS_LAVA | CONTENTS_SLIME); + if(rs_trace.fraction == 1.0 ) return true; + return false; +} + +// +// BOT用可視判定 2 +// + +qboolean Bot_trace2 (edict_t *ent,vec3_t ttz) +{ + trace_t rs_trace; + vec3_t ttx; + VectorCopy (ent->s.origin,ttx); + if(ent->maxs[2] >=32) ttx[2] += 24; + else ttx[2] -= 12; + + rs_trace = gi.trace (ttx, NULL, NULL, ttz ,ent, CONTENTS_SOLID | CONTENTS_LAVA | CONTENTS_SLIME /*| CONTENTS_TRANSLUCENT*/); + if(rs_trace.fraction != 1.0 ) return false; + return true; +} + +// +// BOT用可視判定 3 +// + +qboolean Bot_traceS (edict_t *ent,edict_t *other) +{ + trace_t rs_trace; + vec3_t start,end; + int mycont; + + + VectorCopy(ent->s.origin,start); + VectorCopy(other->s.origin,end); + + start[2] += ent->viewheight - 8; + end[2] += other->viewheight - 8; + + if(Bot[ent->client->zc.botindex].param[BOP_NOSTHRWATER]) goto WATERMODE; + + rs_trace = gi.trace (start, NULL, NULL, end ,ent,CONTENTS_SOLID | CONTENTS_WINDOW | CONTENTS_LAVA | CONTENTS_SLIME); + + if(rs_trace.fraction != 1.0 ) return false; + return true; + +WATERMODE: + mycont = gi.pointcontents(start); + + if((mycont & CONTENTS_WATER) && !other->waterlevel) + { + rs_trace = gi.trace (end, NULL, NULL, start ,ent,CONTENTS_SOLID | CONTENTS_WINDOW | CONTENTS_LAVA | CONTENTS_SLIME | CONTENTS_WATER); + if(rs_trace.surface) + { + if(rs_trace.surface->flags & SURF_WARP) return false; + } + rs_trace = gi.trace (start, NULL, NULL, end ,ent,CONTENTS_SOLID | CONTENTS_WINDOW | CONTENTS_LAVA | CONTENTS_SLIME); + if(rs_trace.fraction != 1.0 ) return false; + return true; + } + else if((mycont & CONTENTS_WATER) && other->waterlevel) + { + VectorCopy(other->s.origin,end); + end[2] -= 16; + rs_trace = gi.trace (start, NULL, NULL, end ,ent,CONTENTS_SOLID | CONTENTS_WINDOW ); + if(rs_trace.fraction != 1.0 ) return false; + return true; + } + + if(other->waterlevel) + { + VectorCopy(other->s.origin,end); + end[2] += 32; + rs_trace = gi.trace (start, NULL, NULL, end ,ent,CONTENTS_SOLID | CONTENTS_WINDOW | CONTENTS_WATER); + if(rs_trace.surface) + { + if(rs_trace.surface->flags & SURF_WARP) return false; + } +// if(rs_trace.fraction != 1.0 ) return false; +// return true; + } + + rs_trace = gi.trace (start, NULL, NULL, end ,ent,CONTENTS_SOLID | CONTENTS_WINDOW | CONTENTS_LAVA | CONTENTS_SLIME); + if(rs_trace.fraction != 1.0 ) return false; + return true; +} + + + + +// +// VEC値からyawを得る +// + +float Get_yaw (vec3_t vec) +{ + vec3_t out; + double yaw; + + VectorCopy(vec,out); + out[2] = 0; + VectorNormalize2 (out, out); + + yaw = acos((double) out[0]); + // yaw = (float) yaw; + yaw = yaw / M_PI * 180; + + if(asin((double) out[1]) < 0 ) yaw *= -1; + + return (float)yaw; +} + +float Get_pitch (vec3_t vec) +{ + vec3_t out; + float pitch; + + VectorNormalize2 (vec, out); + + pitch = acos((double) out[2]); + // yaw = (float) yaw; + pitch = ((float)pitch) / M_PI * 180; + +// if(asin((double) out[0]) < 0 ) pitch *= -1; + + pitch -= 90; + if(pitch < -180) pitch += 360; + + return pitch; +} + +// +// VEC値とyaw値の角度差を得る +// + +float Get_vec_yaw (vec3_t vec,float yaw) +{ + float vecsyaw; + + vecsyaw = Get_yaw (vec); + + if(vecsyaw > yaw) vecsyaw -= yaw; + else vecsyaw = yaw - vecsyaw; + + if(vecsyaw >180 ) vecsyaw = 360 - vecsyaw; + + return vecsyaw; +} + +//yaw に対するvecの相対 +float Get_vec_yaw2 (vec3_t vec,float yaw) +{ + float vecsyaw; + + vecsyaw = Get_yaw (vec); + + vecsyaw -= yaw; + if(vecsyaw >180 ) vecsyaw -= 360; + else if(vecsyaw <-180 ) vecsyaw += 360; + + return vecsyaw; +} + diff --git a/src/bot.h b/src/bot.h new file mode 100644 index 0000000..02e06f0 --- /dev/null +++ b/src/bot.h @@ -0,0 +1,429 @@ +#ifndef BOTHEAD +#define BOTHEAD +#include "g_local.h" + +//general func +void player_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point); + +//bot spawn & remove +qboolean SpawnBot(int i); +void Bot_LevelChange(); +void Load_BotInfo(); +void Bot_SpawnCall(); +void RemoveBot(); +void SpawnBotReserving(); + +//weapon +void Weapon_Blaster (edict_t *ent); +void Weapon_Shotgun (edict_t *ent); +void Weapon_SuperShotgun (edict_t *ent); +void Weapon_Machinegun (edict_t *ent); +void Weapon_Chaingun (edict_t *ent); +void Weapon_HyperBlaster (edict_t *ent); +void Weapon_RocketLauncher (edict_t *ent); +void Weapon_Grenade (edict_t *ent); +void Weapon_GrenadeLauncher (edict_t *ent); +void Weapon_Railgun (edict_t *ent); +void Weapon_BFG (edict_t *ent); +void CTFWeapon_Grapple (edict_t *ent); + +// RAFAEL +void Weapon_Ionripper (edict_t *ent); +void Weapon_Phalanx (edict_t *ent); +void Weapon_Trap (edict_t *ent); + +// wideuse +qboolean Bot_trace (edict_t *ent,edict_t *other); +qboolean Bot_trace2 (edict_t *ent,vec3_t ttz); +float Get_yaw (vec3_t vec); // +float Get_pitch (vec3_t vec); // +float Get_vec_yaw (vec3_t vec,float yaw); +void ShowGun(edict_t *ent); +void SpawnItem3 (edict_t *ent, gitem_t *item); +int Bot_moveT ( edict_t *ent,float ryaw,vec3_t pos,float dist,float *bottom); +void Set_BotAnim(edict_t *ent,int anim,int frame,int end); +void plat_go_up (edict_t *ent); +int Get_KindWeapon(gitem_t *it); +qboolean TargetJump(edict_t *ent,vec3_t tpos); +qboolean Bot_traceS (edict_t *ent,edict_t *other); +qboolean Bot_Fall(edict_t *ent,vec3_t pos,float dist); + +void SelectSpawnPoint (edict_t *ent, vec3_t origin, vec3_t angles); +void ClientUserinfoChanged (edict_t *ent, char *userinfo); +void CopyToBodyQue (edict_t *ent); + +//route util +qboolean TraceX (edict_t *ent,vec3_t p2); +void Move_LastRouteIndex(); +void Get_RouteOrigin(int index,vec3_t pos); + +//Bot Func +void ZigockJoinMenu(edict_t *ent); +qboolean ZigockStartClient(edict_t *ent); +void Cmd_AirStrike(edict_t *ent); +void BotEndServerFrame (edict_t *ent); +void SpawnItem2 (edict_t *ent, gitem_t *item); +void Get_WaterState(edict_t *ent); +void Bot_Think (edict_t *self); +void PutBotInServer (edict_t *ent); +void SpawnBotReserving2(int *red,int *blue); + +//Combat AI +void Combat_Level0(edict_t *ent,int foundedenemy,int enewep,float aim,float distance,int skill); +void Combat_LevelX(edict_t *ent,int foundedenemy,int enewep,float aim,float distance,int skill); +void UsePrimaryWeapon(edict_t *ent); + +//Explotion Index +void UpdateExplIndex(edict_t* ent); + +//flag +qboolean ZIGDrop_Flag(edict_t *ent, gitem_t *item); + +//p_view.c +void BotEndServerFrame (edict_t *ent); + +//Bot AI routine +void Bots_Move_NORM (edict_t *ent); //normal AI + +//spawn +void SetBotFlag1(edict_t *ent); //チーム1の旗 +void SetBotFlag2(edict_t *ent); //チーム2の旗 +void CTFSetupNavSpawn(); //ナビの設置 + +//ctf +void CTFJobAssign (void); //job assign + +//VWep +// ### Hentai ### BEGIN +//void ShowGun(edict_t *ent); +// ### Hentai ### END + +//---------------------------------------------------------------- + +//moving speed +#define MOVE_SPD_WALK 20 +#define MOVE_SPD_RUN 32 +#define MOVE_SPD_DUCK 10 +#define MOVE_SPD_WATER 16 +#define MOVE_SPD_JUMP 32 +#define VEL_BOT_JUMP 340//341 //jump vel +//#define VEL_BOT_ROCJ 500 //roc jump +#define VEL_BOT_WJUMP 341//150 //waterjump vel +#define VEL_BOT_LADRUP 200 //ladderup vel +#define VEL_BOT_WLADRUP 200 //0 //water ladderup gain + + +//classes +#define CLS_NONE 0 //normal +#define CLS_ALPHA 1 //sniper +#define CLS_BETA 2 +#define CLS_GAMMA 3 +#define CLS_DELTA 4 +#define CLS_EPSILON 5 +#define CLS_ZETA 6 +#define CLS_ETA 7 + +// function's state P +#define PSTATE_TOP 0 +#define PSTATE_BOTTOM 1 +#define PSTATE_UP 2 +#define PSTATE_DOWN 3 + +#define PDOOR_TOGGLE 32 + +// height +#define TOP_LIMIT 52 +#define TOP_LIMIT_WATER 100 +#define BOTTOM_LIMIT -52 +#define BOTTOM_LIMIT_WATER -8190 +#define BOTTOM_LIMITM -300 + +//waterstate +#define WAS_NONE 0 +#define WAS_FLOAT 1 +#define WAS_IN 2 + +//route +//chaining pod state +#define GRS_NORMAL 0 +#define GRS_ONROTATE 1 +#define GRS_TELEPORT 2 +#define GRS_ITEMS 3 +#define GRS_ONPLAT 4 +#define GRS_ONTRAIN 5 +#define GRS_ONDOOR 6 +#define GRS_PUSHBUTTON 7 + +#define GRS_GRAPSHOT 20 +#define GRS_GRAPHOOK 21 +#define GRS_GRAPRELEASE 22 + +#define GRS_REDFLAG -10 +#define GRS_BLUEFLAG -11 + +#define POD_LOCKFRAME 15 //20 +#define POD_RELEFRAME 20 //25 + +#define MAX_SEARCH 12 //max search count/FRAMETIME +#define MAX_DOORSEARCH 10 + +//trace param +#define TRP_NOT 0 //don't trace +#define TRP_NORMAL 1 //trace normal +#define TRP_ANGLEKEEP 2 //trace and keep angle +#define TRP_MOVEKEEP 3 //angle and move vec keep but move +#define TRP_ALLKEEP 4 //don't move + +// bot spawning status +#define BOT_SPAWNNOT 0 +#define BOT_SPRESERVED 1 +#define BOT_SPAWNED 2 +#define BOT_NEXTLEVEL 3 + +//combat +#define AIMING_POSGAP 5 +#define AIMING_ANGLEGAP_S 0.75 //shot gun +#define AIMING_ANGLEGAP_M 0.35 //machine gun + +//team play state +#define TMS_NONE 0 +#define TMS_LEADER 1 +#define TMS_FOLLOWER 2 + +//ctf state +#define CTFS_NONE 0 +#define CTFS_CARRIER 1 +#define CTFS_ROAMER 2 +#define CTFS_OFFENCER 3 +#define CTFS_DEFENDER 4 +#define CTFS_SUPPORTER 5 + +#define FOR_FLAG1 1 +#define FOR_FLAG2 2 + +//fire---------------------------------------------------------- +#define FIRE_SLIDEMODE 0x00000001 //slide with route +#define FIRE_PRESTAYFIRE 0x00000002 //X pre don't move fire +#define FIRE_STAYFIRE 0x00000004 //X don't move +#define FIRE_CHIKEN 0x00000008 //X chiken fire +#define FIRE_RUSH 0x00000010 //X rush +#define FIRE_JUMPNRUSH 0x00000020 // +#define FIRE_ESTIMATE 0x00000040 //X estimate 予測 +#define FIRE_SCATTER 0x00000080 //scatter バラ撒き +#define FIRE_RUNNIN 0x00000100 //run & shot(normal) +#define FIRE_JUMPROC 0x00000200 //X ジャンプふぁいあ + +#define FIRE_REFUGE 0x00001000 //X 避難 +#define FIRE_EXPAVOID 0x00002000 //X 爆発よけ + +#define FIRE_QUADUSE 0x00004000 //X Quad時の連射武器選択 +#define FIRE_AVOIDINV 0x00008000 //X 相手がペンタの時逃げる + +#define FIRE_BFG 0x00010000 //X 普通にBFGを撃つ + +#define FIRE_SHIFT_R 0x00020000 //X 右スライド +#define FIRE_SHIFT_L 0x00040000 //X 左スライド + +#define FIRE_SHIFT (FIRE_SHIFT_R | FIRE_SHIFT_L)//X 右スライド + +#define FIRE_REFLECT 0x00080000 // 壁に反射させる + +#define FIRE_IGNORE 0x10000000 //無視して逃げる + +// means of death + +#define MOD_SNIPERAIL 50 //SNIPE RAIL +#define MOD_LOCMISSILE 51 //LOCKON MISSILE + +#define MOD_BFG100K 52 // + +#define MOD_AIRSTRIKE 70 //AIRSTRIKE +//---------------------------------------------------------------- +//general status list +#define STS_IDLE 0x00000000 //normal running +#define STS_THINK 0x00000001 //stand and analise +#define STS_LADDERUP 0x00000002 //crimb the ladder +#define STS_ROCJ 0x00000004 //rocket jumping +#define STS_TURBOJ 0x00000008 //turbo jump +#define STS_WATERJ 0x00000010 //turbo jump + +#define STS_SJMASK (STS_ROCJ | STS_TURBOJ | STS_WATERJ) //special jump mask +#define STS_SJMASKEXW (STS_ROCJ | STS_TURBOJ ) //special jump mask ex. water + + +#define STS_TALKING 0x00000200 //talking +#define STS_ESC_WXPL 0x00000400 //escape from explode + +#define STS_MOVE_WPOINT 0x00000800 //moving waiting point +//#define STS_W_EXPL 0x00001000 //wait for end of explode + +//wait +#define STS_W_DONT 0x00001000 //don't wait door or plat +#define STS_W_DOOROPEN 0x00002000 //wait for door open or down to bottom +#define STS_W_ONPLAT 0x00004000 //wait for plat or door reach to da top +#define STS_W_ONDOORUP 0x00008000 //wait for door reach to da top +#define STS_W_ONDOORDWN 0x00010000 //wait for door reach to da bottom +#define STS_W_ONTRAIN 0x00020000 //wait for plat or door reach to da top +#define STS_W_COMETRAIN 0x00040000 //wait for train come +#define STS_W_COMEPLAT 0x00080000 //wait for plat come + +#define STS_WAITS (STS_W_DONT | STS_W_DOOROPEN | STS_W_COMEPLAT | STS_W_ONPLAT | STS_W_ONDOORUP | STS_W_ONDOORDWN | STS_W_ONTRAIN) +#define STS_WAITSMASK (STS_W_DOOROPEN | STS_W_ONPLAT | STS_W_ONDOORUP | STS_W_COMEPLAT | STS_W_ONDOORDWN | STS_W_ONTRAIN) +#define STS_WAITSMASK2 (STS_W_ONDOORDWN |STS_W_ONDOORUP | STS_W_ONPLAT | STS_W_ONTRAIN) +#define STS_WAITSMASKCOM (STS_W_DOOROPEN | STS_W_ONPLAT | STS_W_ONDOORUP | STS_W_ONDOORDWN | STS_W_ONTRAIN) +//---------------------------------------------------------------- +//general status list +#define CTS_ENEM_NSEE 0x00000001 //have enemy but can't see +#define CTS_AGRBATTLE 0x00000002 //aglessive battle +#define CTS_ESCBATTLE 0x00000004 //escaping battle(item want) +#define CTS_HIPBATTLE 0x00000008 //high position battle(camp) + + +//shoot +#define CTS_PREAIMING 0x00000010 //prepare for snipe or lockon +#define CTS_AIMING 0x00000020 //aimning for snipe or lockon +#define CTS_GRENADE 0x00000040 //hand grenade mode +#define CTS_JUMPSHOT 0x00000080 //jump shot + +#define CTS_COMBS (CTS_AGRBATTLE | CTS_ESCBATTLE | CTS_HIPBATTLE | CTS_ENEM_NSEE) + +//---------------------------------------------------------------- +//route struct +#define MAXNODES 10000 //5000 added 5000 pods +#define MAXLINKPOD 6 //don't modify this +#define CTF_FLAG1_FLAG 0x0000 +#define CTF_FLAG2_FLAG 0x8000 + +typedef struct +{ + vec3_t Pt; //target point + union + { + vec3_t Tcourner; //target courner(train and grap-shot only) + unsigned short linkpod[MAXLINKPOD]; //(GRS_NORMAL,GRS_ITEMS only 0 = do not select pod) + }; + edict_t *ent; //target ent + short index; //index num + short state; //targetstate +} route_t; + +//---------------------------------------------------------------- +//bot info struct +#define MAXBOTS 64 +#define MAXBOP 16 + +// bot params +#define BOP_WALK 0 //flags +#define BOP_AIM 1 //aiming +#define BOP_PICKUP 2 //frq PICKUP +#define BOP_OFFENCE 3 //chiken fire etc. +#define BOP_COMBATSKILL 4 //combat skill +#define BOP_ROCJ 5 //rocket jump +#define BOP_REACTION 6 //reaction skill exp. frq SEARCH ENEMY +#define BOP_VRANGE 7 //V-View of RANGE 縦 +#define BOP_HRANGE 8 //H-View of Range 横 +#define BOP_PRIWEP 9 //primary weapon +#define BOP_SECWEP 10 //secondary weapon +#define BOP_DODGE 11 //dodge +#define BOP_ESTIMATE 12 //estimate +#define BOP_NOISECHK 13 //noisecheck +#define BOP_NOSTHRWATER 14 //can't see through water +#define BOP_TEAMWORK 15 //teamwork + +typedef struct +{ + char netname[21]; //netname + char model[21]; //model + char skin[21]; //skin + int spflg; //spawned flag 0-not 1-waiting 2-spawned + int team; //team NO. 0-noteam 1-RED 2-BLUE + int arena; //if arena is on + unsigned char param[MAXBOP]; //Params +} botinfo_t; + +//---------------------------------------------------------------- +//message section name +#define MESS_DEATHMATCH "[MessDeathMatch]" +#define MESS_CHAIN_DM "[MessChainDM]" +#define MESS_CTF "[MessCTF]" +#define MESS_CHAIN_CTF "[MessChainCTF]" + +//---------------------------------------------------------------- +//bot list section name +#define BOTLIST_SECTION_DM "[BotList]" +#define BOTLIST_SECTION_TM "[BotListTM]" + +//---------------------------------------------------------------- +#define MAX_BOTSKILL 10 + +#define FALLCHK_LOOPMAX 30 + +//laser Index +#define MAX_LASERINDEX 30 +extern edict_t* LaserIndex[MAX_LASERINDEX]; + +//Explotion Index +#define MAX_EXPLINDEX 12 +extern edict_t* ExplIndex[MAX_EXPLINDEX]; +// + + +extern int cumsindex; +extern int targetindex; //debugtarget + +extern int ListedBotCount; //bot count of list + +extern int SpawnWaitingBots; +extern char ClientMessage[MAX_STRING_CHARS]; +extern botinfo_t Bot[MAXBOTS]; +extern route_t Route[MAXNODES]; +extern int CurrentIndex; +extern float JumpMax; +extern int botskill; +extern int trace_priority; +extern int FFlg[MAX_BOTSKILL]; + +extern int ListedBots; + +//for avoid abnormal frame error +extern int skullindex; +extern int headindex; + +extern gitem_t *zflag_item; +extern edict_t *zflag_ent; +extern int zigflag_spawn; + +//item index +extern int mpindex[MPI_INDEX]; + +//PON-CTF +extern edict_t *bot_team_flag1; +extern edict_t *bot_team_flag2; +//PON-CTF + +//pre searched items +extern gitem_t *Fdi_GRAPPLE; +extern gitem_t *Fdi_BLASTER; +extern gitem_t *Fdi_SHOTGUN; +extern gitem_t *Fdi_SUPERSHOTGUN; +extern gitem_t *Fdi_MACHINEGUN; +extern gitem_t *Fdi_CHAINGUN; +extern gitem_t *Fdi_GRENADES; +extern gitem_t *Fdi_GRENADELAUNCHER; +extern gitem_t *Fdi_ROCKETLAUNCHER; +extern gitem_t *Fdi_HYPERBLASTER; +extern gitem_t *Fdi_RAILGUN; +extern gitem_t *Fdi_BFG; +extern gitem_t *Fdi_PHALANX; +extern gitem_t *Fdi_BOOMER; +extern gitem_t *Fdi_TRAP; + +extern gitem_t *Fdi_SHELLS; +extern gitem_t *Fdi_BULLETS; +extern gitem_t *Fdi_CELLS; +extern gitem_t *Fdi_ROCKETS; +extern gitem_t *Fdi_SLUGS; +extern gitem_t *Fdi_MAGSLUGS; + +extern float ctfjob_update; +#endif \ No newline at end of file diff --git a/src/bot_fire.c b/src/bot_fire.c new file mode 100644 index 0000000..ce24a06 --- /dev/null +++ b/src/bot_fire.c @@ -0,0 +1,1822 @@ +#include "bot.h" +#include "q_shared.h" +#include "m_player.h" + +//====================================================================== +//aim決定 +//ent entity +//aim aimスキル +//yaw dist +//wep weapon +void Get_AimAngle(edict_t *ent,float aim,float dist,int weapon) +{ + edict_t *target; + vec3_t targaim,v; + trace_t rs_trace; + + target = ent->client->zc.first_target; + + switch(weapon) + { + //即判定 + case WEAP_SHOTGUN: + case WEAP_SUPERSHOTGUN: + case WEAP_RAILGUN: + if(target != ent->client->zc.last_target) + { + if(target->svflags & SVF_MONSTER) + { + VectorSubtract(target->s.old_origin,target->s.origin,targaim); + } + else + { + VectorCopy(target->velocity,targaim); + VectorInverse (targaim); + } + VectorNormalize(targaim); + VectorMA(target->s.origin,random() * aim * AIMING_POSGAP * random(),targaim,targaim); + } + else + { + VectorSubtract(ent->client->zc.last_pos,target->s.origin,targaim); +// VectorScale (targaim, vec_t scale, vec3_t out) + VectorMA(target->s.origin,aim * /*VectorLength(targaim)**/ random(),targaim,targaim); + } + VectorSubtract(targaim,ent->s.origin,targaim); + + ent->s.angles[YAW] = Get_yaw(targaim); + ent->s.angles[PITCH] = Get_pitch(targaim); + + ent->s.angles[YAW] += aim * AIMING_ANGLEGAP_S * (random() - 0.5) *2; + if(ent->s.angles[YAW] > 180) ent->s.angles[YAW] -= 360; + else if(ent->s.angles[YAW] < -180) ent->s.angles[YAW] += 360; + + ent->s.angles[PITCH] += (aim * AIMING_ANGLEGAP_S * (random() - 0.5) * 2); + if(ent->s.angles[PITCH] > 90) ent->s.angles[PITCH] = 90; + else if(ent->s.angles[PITCH] < -90) ent->s.angles[PITCH] = -90; + break; + + case WEAP_MACHINEGUN: + case WEAP_CHAINGUN: + if(target != ent->client->zc.last_target) + { + if(target->svflags & SVF_MONSTER) + { + VectorSubtract(target->s.old_origin,target->s.origin,targaim); + } + else + { + VectorCopy(target->velocity,targaim); + VectorInverse (targaim); + } + VectorNormalize(targaim); + VectorMA(target->s.origin,random() * aim * AIMING_POSGAP,targaim,targaim); + } + else + { + VectorSubtract(ent->client->zc.last_pos,target->s.origin,targaim); + VectorMA(target->s.origin,random() * aim /** VectorLength(targaim)*/,targaim,targaim); + } + VectorSubtract(targaim,ent->s.origin,targaim); + + ent->s.angles[YAW] = Get_yaw(targaim); + ent->s.angles[PITCH] = Get_pitch(targaim); + + ent->s.angles[YAW] += aim * AIMING_ANGLEGAP_M * (random() - 0.5) *2; + if(ent->s.angles[YAW] > 180) ent->s.angles[YAW] -= 360; + else if(ent->s.angles[YAW] < -180) ent->s.angles[YAW] += 360; + + ent->s.angles[PITCH] += (aim * AIMING_ANGLEGAP_M * (random() - 0.5) * 2); + if(ent->s.angles[PITCH] > 90) ent->s.angles[PITCH] = 90; + else if(ent->s.angles[PITCH] < -90) ent->s.angles[PITCH] = -90; + break; + + case WEAP_BLASTER: + case WEAP_GRENADES: + case WEAP_GRENADELAUNCHER: + case WEAP_ROCKETLAUNCHER: + case WEAP_PHALANX: + case WEAP_BOOMER: + if(target != ent->client->zc.last_target) + { + if(target->svflags & SVF_MONSTER) + { + VectorSubtract(target->s.origin,target->s.old_origin,targaim); + } + else + { + VectorCopy(target->velocity,targaim); + targaim[0] *= 32; + targaim[1] *= 32; + targaim[2] *= 32; + } + VectorNormalize(targaim); + VectorMA(target->s.origin,(11 - aim) * dist/25,targaim,targaim); + } + else + { + VectorSubtract(target->s.origin,ent->client->zc.last_pos,targaim); + targaim[2] /= 2; + VectorMA(target->s.origin,- aim * random() + dist/75,targaim,targaim); + } + rs_trace = gi.trace(target->s.origin,NULL,NULL,targaim,target,MASK_SHOT); + VectorCopy(rs_trace.endpos,targaim); + + if(weapon == WEAP_GRENADELAUNCHER + || weapon == WEAP_ROCKETLAUNCHER + || weapon == WEAP_PHALANX) + { + if(targaim[2] < (ent->s.origin[2] + JumpMax)) + { + targaim[2] -= 24; + + VectorCopy(ent->s.origin,v); + v[2] += ent->viewheight - 8; + rs_trace = gi.trace(v,NULL,NULL,targaim,ent,MASK_SHOT); + if(rs_trace.fraction != 1.0) targaim[2] += 24; + } + else if(targaim[2] > (ent->s.origin[2] + JumpMax)) targaim[2] += 5; + } + + VectorSubtract(targaim,ent->s.origin,targaim); + + ent->s.angles[YAW] = Get_yaw(targaim); + ent->s.angles[PITCH] = Get_pitch(targaim); + + ent->s.angles[YAW] += aim * AIMING_ANGLEGAP_M * (random() - 0.5) *2; + if(ent->s.angles[YAW] > 180) ent->s.angles[YAW] -= 360; + else if(ent->s.angles[YAW] < -180) ent->s.angles[YAW] += 360; + + ent->s.angles[PITCH] += (aim * AIMING_ANGLEGAP_M * (random() - 0.5) * 2); + if(ent->s.angles[PITCH] > 90) ent->s.angles[PITCH] = 90; + else if(ent->s.angles[PITCH] < -90) ent->s.angles[PITCH] = -90; + break; + + case WEAP_HYPERBLASTER: + if(target != ent->client->zc.last_target) + { + if(target->svflags & SVF_MONSTER) + { + VectorSubtract(target->s.origin,target->s.old_origin,targaim); + } + else + { + VectorCopy(target->velocity,targaim); + targaim[0] *= 32; + targaim[1] *= 32; + targaim[2] *= 32; + } + VectorNormalize(targaim); + VectorMA(target->s.origin,(11 - aim) * dist/100,targaim,targaim); + } + else + { + VectorSubtract(target->s.origin,ent->client->zc.last_pos,targaim); + targaim[2] /= 2; + VectorMA(target->s.origin,- aim + dist/115,targaim,targaim); + } + rs_trace = gi.trace(target->s.origin,NULL,NULL,targaim,target,MASK_SHOT); + VectorCopy(rs_trace.endpos,targaim); + + VectorSubtract(targaim,ent->s.origin,targaim); + + ent->s.angles[YAW] = Get_yaw(targaim); + ent->s.angles[PITCH] = Get_pitch(targaim); + + ent->s.angles[YAW] += aim * AIMING_ANGLEGAP_M * (random() - 0.5) *2; + if(ent->s.angles[YAW] > 180) ent->s.angles[YAW] -= 360; + else if(ent->s.angles[YAW] < -180) ent->s.angles[YAW] += 360; + + ent->s.angles[PITCH] += (aim * AIMING_ANGLEGAP_M * (random() - 0.5) * 2); + if(ent->s.angles[PITCH] > 90) ent->s.angles[PITCH] = 90; + else if(ent->s.angles[PITCH] < -90) ent->s.angles[PITCH] = -90; + break; + + case WEAP_BFG: + VectorCopy(ent->client->zc.vtemp,targaim); + VectorSubtract(targaim,ent->s.origin,targaim); + + ent->s.angles[YAW] = Get_yaw(targaim); + ent->s.angles[PITCH] = Get_pitch(targaim); + break; + default: + break; + } +} + + + +//====================================================================== +//武器使用可能? +int CanUsewep(edict_t *ent,int weapon) +{ + gitem_t *item; + gclient_t *client; + int mywep,ammoindex; + + client = ent->client; + + mywep = Get_KindWeapon(client->pers.weapon); + + switch(weapon) + { + case WEAP_BLASTER: + item = Fdi_BLASTER;//FindItem("Blaster"); + if(client->pers.inventory[ITEM_INDEX(item)]) + { + if(mywep == WEAP_BLASTER || client->weaponstate == WEAPON_READY) + { + item->use(ent,item); + if(client->weaponstate == WEAPON_READY) return true; + else return 2; + } + } + break; + + case WEAP_SHOTGUN: + item = Fdi_SHOTGUN;//FindItem("Shotgun"); + ammoindex = ITEM_INDEX(Fdi_SHELLS/*FindItem("Shells")*/); + if(client->pers.inventory[ITEM_INDEX(item)] + && client->pers.inventory[ammoindex] > 0) + { + if(mywep == WEAP_SHOTGUN || client->weaponstate == WEAPON_READY) + { + item->use(ent,item); + if(client->weaponstate == WEAPON_READY) return true; + else return 2; + } + } + break; + + case WEAP_SUPERSHOTGUN: + item = Fdi_SUPERSHOTGUN;//FindItem("Super Shotgun"); + ammoindex = ITEM_INDEX(Fdi_SHELLS/*FindItem("Shells")*/); + + if( client->pers.inventory[ITEM_INDEX(item)] + && client->pers.inventory[ammoindex] > 1) + { + if(mywep == WEAP_SUPERSHOTGUN || client->weaponstate == WEAPON_READY) + { + item->use(ent,item); + if(client->weaponstate == WEAPON_READY) return true; + else return 2; + } + } + break; + + case WEAP_MACHINEGUN: + item = Fdi_MACHINEGUN;//FindItem("Machinegun"); + ammoindex = ITEM_INDEX(Fdi_BULLETS/*FindItem("Bullets")*/); + + if( client->pers.inventory[ITEM_INDEX(item)] + && client->pers.inventory[ammoindex] > 0) + { + if(client->pers.weapon != item) item->use(ent,item); + + if(mywep == WEAP_MACHINEGUN || client->weaponstate == WEAPON_READY + || client->weaponstate == WEAPON_FIRING) + { +// if(client->pers.weapon == item) return true; +// else {item->use(ent,item); return 2;} + if(client->weaponstate == WEAPON_READY || client->weaponstate == WEAPON_FIRING) return true; + else return 2; + } + } + break; + + case WEAP_CHAINGUN: + item = FindItem("Chaingun"); + ammoindex = ITEM_INDEX(Fdi_BULLETS/*FindItem("Bullets")*/); + + if( client->pers.inventory[ITEM_INDEX(item)] + && client->pers.inventory[ammoindex] > 0) + { + if(mywep == WEAP_CHAINGUN || client->weaponstate == WEAPON_READY) + { + item->use(ent,item); + if(client->weaponstate == WEAPON_READY || client->weaponstate == WEAPON_FIRING) return true; + else return 2; + } + } + break; + + case WEAP_GRENADES: + item = Fdi_GRENADES;//FindItem("Grenades"); + ammoindex = ITEM_INDEX(Fdi_GRENADES/*FindItem("Grenades")*/); + + if(client->pers.inventory[ammoindex] > 0) + { + if(mywep == WEAP_GRENADES || client->weaponstate == WEAPON_READY) + { + item->use(ent,item); + if(client->weaponstate == WEAPON_READY) return true; + else return 2; + } + } + break; + + case WEAP_TRAP: + item = Fdi_TRAP;//FindItem("Trap"); + ammoindex = ITEM_INDEX(Fdi_TRAP/*FindItem("Trap")*/); + + if(client->pers.inventory[ammoindex] > 0) + { + if(mywep == WEAP_GRENADES || client->weaponstate == WEAPON_READY) + { + item->use(ent,item); + if(client->weaponstate == WEAPON_READY) return true; + else return 2; + } + } + break; + + case WEAP_GRENADELAUNCHER: + item = Fdi_GRENADELAUNCHER;//FindItem("Grenade Launcher"); + ammoindex = ITEM_INDEX(Fdi_GRENADES/*FindItem("Grenades")*/); + + if( client->pers.inventory[ITEM_INDEX(item)] + && client->pers.inventory[ammoindex] > 0) + { + if(mywep == WEAP_GRENADELAUNCHER || client->weaponstate == WEAPON_READY) + { + item->use(ent,item); + if(client->weaponstate == WEAPON_READY) return true; + else return 2; + } + } + break; + + case WEAP_ROCKETLAUNCHER: + item = Fdi_ROCKETLAUNCHER;//FindItem("Rocket Launcher"); + ammoindex = ITEM_INDEX(Fdi_ROCKETS/*FindItem("Rockets")*/); + + if( client->pers.inventory[ITEM_INDEX(item)] + && client->pers.inventory[ammoindex] > 0) + { + if(mywep == WEAP_ROCKETLAUNCHER || client->weaponstate == WEAPON_READY) + { + item->use(ent,item); + if(client->weaponstate == WEAPON_READY) return true; + else return 2; + } + } + break; + + case WEAP_HYPERBLASTER: + item = Fdi_HYPERBLASTER;//FindItem("HyperBlaster"); + ammoindex = ITEM_INDEX(Fdi_CELLS/*FindItem("Cells")*/); + + if( client->pers.inventory[ITEM_INDEX(item)] + && client->pers.inventory[ammoindex] > 0) + { + if(mywep == WEAP_HYPERBLASTER || client->weaponstate == WEAPON_READY) + { + item->use(ent,item); + if(client->weaponstate == WEAPON_READY || client->weaponstate == WEAPON_FIRING) return true; + else return 2; + } + } + break; + + case WEAP_BOOMER: + item = Fdi_BOOMER;//FindItem("Ionripper"); + ammoindex = ITEM_INDEX(Fdi_CELLS/*FindItem("Cells")*/); + + if( client->pers.inventory[ITEM_INDEX(item)] + && client->pers.inventory[ammoindex] > 0) + { + if(mywep == WEAP_BOOMER || client->weaponstate == WEAPON_READY) + { + item->use(ent,item); + if(client->weaponstate == WEAPON_READY || client->weaponstate == WEAPON_FIRING) return true; + else return 2; + } + } + break; + + case WEAP_RAILGUN: + item = Fdi_RAILGUN;//FindItem("Railgun"); + ammoindex = ITEM_INDEX(Fdi_SLUGS/*FindItem("Slugs")*/); + + if( client->pers.inventory[ITEM_INDEX(item)] + && client->pers.inventory[ammoindex] > 0) + { + if(mywep == WEAP_RAILGUN || client->weaponstate == WEAPON_READY) + { + item->use(ent,item); + if(client->weaponstate == WEAPON_READY) return true; + else return 2; + } + } + break; + + case WEAP_PHALANX: + item = Fdi_PHALANX;//FindItem("Phalanx"); + ammoindex = ITEM_INDEX(Fdi_MAGSLUGS/*FindItem("Mag Slug")*/); + + if( client->pers.inventory[ITEM_INDEX(item)] + && client->pers.inventory[ammoindex] > 0) + { + if(mywep == WEAP_PHALANX || client->weaponstate == WEAPON_READY) + { + item->use(ent,item); + if(client->weaponstate == WEAPON_READY) return true; + else return 2; + } + } + break; + + case WEAP_BFG: + item = Fdi_BFG;//FindItem("BFG10K"); + ammoindex = ITEM_INDEX(Fdi_CELLS/*FindItem("Cells")*/); + + if( client->pers.inventory[ITEM_INDEX(item)] + && client->pers.inventory[ammoindex] >= 50) + { + if(mywep == WEAP_BFG || client->weaponstate == WEAPON_READY) + { + item->use(ent,item); + if(client->weaponstate == WEAPON_READY) return true; + else return 2; + } + } + break; + default: //case WEAP_BLASTER: + item = Fdi_BLASTER;//FindItem("Blaster"); + if(client->pers.inventory[ITEM_INDEX(item)]) + { + if(mywep == WEAP_BLASTER || client->weaponstate == WEAPON_READY) + { + item->use(ent,item); + if(client->weaponstate == WEAPON_READY) return true; + else return 2; + } + } + break; + } + return false; +} + +//------------------------------------------------------------ + +// Use BFG + +//------------------------------------------------------------ +qboolean B_UseBfg(edict_t *ent,edict_t *target,int enewep,float aim,float distance,int skill) +{ + int k,mywep; + zgcl_t *zc; + gclient_t *client; + + client = ent->client; + zc = &client->zc; + + if(k = CanUsewep(ent,WEAP_BFG)) + { + mywep = Get_KindWeapon(client->pers.weapon); + Get_AimAngle(ent,aim,distance,mywep); + if(trace_priority < TRP_ANGLEKEEP) trace_priority = TRP_ANGLEKEEP; + if(k = Bot_traceS(ent,target)) VectorCopy(target->s.origin,zc->vtemp); + + if(FFlg[skill] & FIRE_STAYFIRE) + { + if(k /*&& random() < 0.8*/) + { + client->buttons |= BUTTON_ATTACK; + zc->battlemode |= FIRE_STAYFIRE; //モード遷移 + zc->battlecount = 8 + (int)(10 * random()); + trace_priority = TRP_ALLKEEP; + return true; + } + } + //爆発回避 + else if((FFlg[skill] & FIRE_EXPAVOID) + && distance < 300 /*&& random() < 0.5 */ + && Bot_traceS(ent,target)) + { + if(ent->groundentity || zc->waterstate) + { + zc->battlemode |= FIRE_EXPAVOID; + zc->battlecount = 6 + (int)(6 * random()); + trace_priority = TRP_ALLKEEP; + return true; + } + } + //普通 + else if(!(FFlg[skill] &(FIRE_STAYFIRE | FIRE_EXPAVOID))) + { + if(k /*&& random() < 0.8*/) + { + zc->battlemode |= FIRE_BFG; + zc->battlecount = 6 + (int)(6 * random()); + trace_priority = TRP_ANGLEKEEP; + return true; + } + } + else if((FFlg[skill] & FIRE_EXPAVOID) + && Bot_traceS(ent,target)) + { + if(k /*&& random() < 0.8*/) + { + zc->battlemode |= FIRE_BFG; + zc->battlecount = 6 + (int)(6 * random()); + trace_priority = TRP_ANGLEKEEP; + return true; + } + } + } + return false; +} + +//------------------------------------------------------------ + +// Use Hyper Blaster + +//------------------------------------------------------------ +qboolean B_UseHyperBlaster(edict_t *ent,edict_t *target,int enewep,float aim,float distance,int skill) +{ + int k,mywep; + zgcl_t *zc; + gclient_t *client; + + client = ent->client; + zc = &client->zc; + + if(k = CanUsewep(ent,WEAP_HYPERBLASTER)) + { + mywep = Get_KindWeapon(client->pers.weapon); + Get_AimAngle(ent,aim,distance,mywep); + client->buttons |= BUTTON_ATTACK; + if(trace_priority < TRP_ANGLEKEEP) trace_priority = TRP_ANGLEKEEP; + return true; + } + return false; +} + +//------------------------------------------------------------ + +// Use Phalanx + +//------------------------------------------------------------ +qboolean B_UsePhalanx(edict_t *ent,edict_t *target,int enewep,float aim,float distance,int skill) +{ + int k,mywep; + zgcl_t *zc; + gclient_t *client; + + client = ent->client; + zc = &client->zc; + + if(k = CanUsewep(ent,WEAP_PHALANX)) + { + mywep = Get_KindWeapon(client->pers.weapon); + Get_AimAngle(ent,aim,distance,mywep); + if(trace_priority < TRP_ANGLEKEEP) trace_priority = TRP_ANGLEKEEP; + if((FFlg[skill] & FIRE_PRESTAYFIRE) + && ((distance > 500 && random() < 0.1) || fabs(ent->s.angles[PITCH]) > 45 ) + && Bot_traceS(ent,target) + && (enewep <= WEAP_MACHINEGUN || enewep == WEAP_GRENADES)) + { + if(ent->groundentity || zc->waterstate) + { + zc->battlemode |= FIRE_PRESTAYFIRE; + zc->battlecount = 2 + (int)(6 * random()); + trace_priority = TRP_ALLKEEP; + return true; + } + } + if((FFlg[skill] & FIRE_JUMPROC) && random() < 0.3 + && (target->s.origin[2] - ent->s.origin[2]) < JumpMax + && !(client->ps.pmove.pm_flags && PMF_DUCKED)) + { + if(ent->groundentity && !ent->waterlevel <= 1) + { + if(zc->route_trace) + { + if(Bot_Fall(ent,ent->s.origin,0)) + { + trace_priority = TRP_ALLKEEP; + if(Bot_traceS(ent,target)) client->buttons |= BUTTON_ATTACK; + return true; + } + } + else + { + ent->moveinfo.speed = 0; + ent->velocity[2] += VEL_BOT_JUMP; + gi.sound(ent, CHAN_VOICE, gi.soundindex("*jump1.wav"), 1, ATTN_NORM, 0); + PlayerNoise(ent, ent->s.origin, PNOISE_SELF); //pon + Set_BotAnim(ent,ANIM_JUMP,FRAME_jump1-1,FRAME_jump6); + trace_priority = TRP_ALLKEEP; + if(Bot_traceS(ent,target)) client->buttons |= BUTTON_ATTACK; + return true; + } + } + } + else if((FFlg[skill] & FIRE_EXPAVOID) + && distance < 300 && random() < 0.5 + && Bot_traceS(ent,target)) + { + if(ent->groundentity || zc->waterstate) + { + zc->battlemode |= FIRE_EXPAVOID; + zc->battlecount = 4 + (int)(6 * random()); + trace_priority = TRP_ALLKEEP; + return true; + } + } + if(Bot_traceS(ent,target)) client->buttons |= BUTTON_ATTACK; + return true; + } + return false; +} + + +//------------------------------------------------------------ + +// Use Rocket + +//------------------------------------------------------------ +qboolean B_UseRocket(edict_t *ent,edict_t *target,int enewep,float aim,float distance,int skill) +{ + int k,mywep; + zgcl_t *zc; + gclient_t *client; + + client = ent->client; + zc = &client->zc; + + if(k = CanUsewep(ent,WEAP_ROCKETLAUNCHER)) + { + mywep = Get_KindWeapon(client->pers.weapon); + Get_AimAngle(ent,aim,distance,mywep); + if(trace_priority < TRP_ANGLEKEEP) trace_priority = TRP_ANGLEKEEP; + if((FFlg[skill] & FIRE_PRESTAYFIRE) + && ((distance > 500 && random() < 0.1) || fabs(ent->s.angles[PITCH]) > 45 ) + && Bot_traceS(ent,target) + && (enewep <= WEAP_MACHINEGUN || enewep == WEAP_GRENADES)) + { + if(ent->groundentity || zc->waterstate) + { + zc->battlemode |= FIRE_PRESTAYFIRE; + zc->battlecount = 2 + (int)(6 * random()); + trace_priority = TRP_ALLKEEP; + return true; + } + } + if((FFlg[skill] & FIRE_JUMPROC) && random() < 0.3 + && (target->s.origin[2] - ent->s.origin[2]) < JumpMax + && !(client->ps.pmove.pm_flags && PMF_DUCKED)) + { + if(ent->groundentity && !ent->waterlevel <= 1) + { + if(zc->route_trace) + { + if(Bot_Fall(ent,ent->s.origin,0)) + { + trace_priority = TRP_ALLKEEP; + if(Bot_traceS(ent,target)) client->buttons |= BUTTON_ATTACK; + return true; + } + } + else + { + ent->moveinfo.speed = 0; + + ent->velocity[2] += VEL_BOT_JUMP; + gi.sound(ent, CHAN_VOICE, gi.soundindex("*jump1.wav"), 1, ATTN_NORM, 0); + PlayerNoise(ent, ent->s.origin, PNOISE_SELF); //pon + Set_BotAnim(ent,ANIM_JUMP,FRAME_jump1-1,FRAME_jump6); + trace_priority = TRP_ALLKEEP; + if(Bot_traceS(ent,target)) client->buttons |= BUTTON_ATTACK; + return true; + } + } + } + else if((FFlg[skill] & FIRE_EXPAVOID) + && distance < 300 && random() < 0.5 + && Bot_traceS(ent,target)) + { + if(ent->groundentity || zc->waterstate) + { + zc->battlemode |= FIRE_EXPAVOID; + zc->battlecount = 4 + (int)(6 * random()); + trace_priority = TRP_ALLKEEP; + return true; + } + } + if(Bot_traceS(ent,target)) client->buttons |= BUTTON_ATTACK; + return true; + } + return false; +} + + + +//------------------------------------------------------------ + +// Use Boomer + +//------------------------------------------------------------ +qboolean B_UseBoomer(edict_t *ent,edict_t *target,int enewep,float aim,float distance,int skill) +{ + int k,mywep; + zgcl_t *zc; + gclient_t *client; + + client = ent->client; + zc = &client->zc; + + if(k = CanUsewep(ent,WEAP_BOOMER)) + { + mywep = Get_KindWeapon(client->pers.weapon); + Get_AimAngle(ent,aim,distance,mywep); + client->buttons |= BUTTON_ATTACK; + if(trace_priority < TRP_ANGLEKEEP) trace_priority = TRP_ANGLEKEEP; + return true; + } + return false; +} + +//------------------------------------------------------------ + +// Use Railgun + +//------------------------------------------------------------ +qboolean B_UseRailgun(edict_t *ent,edict_t *target,int enewep,float aim,float distance,int skill) +{ + int k,mywep; + zgcl_t *zc; + gclient_t *client; + + client = ent->client; + zc = &client->zc; + + if(k = CanUsewep(ent,WEAP_RAILGUN)) + { + mywep = Get_KindWeapon(client->pers.weapon); + Get_AimAngle(ent,aim,distance,mywep); + client->buttons |= BUTTON_ATTACK; + if(trace_priority < TRP_ANGLEKEEP) trace_priority = TRP_ANGLEKEEP; + return true; + } + return false; +} + +//------------------------------------------------------------ + +// Use Grenade Launcher + +//------------------------------------------------------------ +qboolean B_UseGrenadeLauncher(edict_t *ent,edict_t *target,int enewep,float aim,float distance,int skill) +{ + int k,mywep; + zgcl_t *zc; + gclient_t *client; + + client = ent->client; + zc = &client->zc; + + if(k = CanUsewep(ent,WEAP_GRENADELAUNCHER)) + { + mywep = Get_KindWeapon(client->pers.weapon); + Get_AimAngle(ent,aim,distance,mywep); + if((FFlg[skill] & FIRE_STAYFIRE) + && random() < 0.3 && target->s.origin[2] < ent->s.origin[2]) + { + if(ent->groundentity || zc->waterstate) + { + if(Bot_traceS(ent,target)) + { + zc->battlemode |= FIRE_STAYFIRE; + zc->battlecount = 5 + (int)(10 * random()); + trace_priority = TRP_ALLKEEP; + client->buttons |= BUTTON_ATTACK; + return true; + } + } + } + else if((FFlg[skill] & FIRE_EXPAVOID) + && distance < 300 && random() < 0.5 + && Bot_traceS(ent,target)) + { + if(ent->groundentity || zc->waterstate) + { + zc->battlemode |= FIRE_EXPAVOID; + zc->battlecount = 2 + (int)(6 * random()); + trace_priority = TRP_ALLKEEP; + return true; + } + } + client->buttons |= BUTTON_ATTACK; + if(trace_priority < TRP_ANGLEKEEP) trace_priority = TRP_ANGLEKEEP; + return true; + } + return false; + +} + +//------------------------------------------------------------ + +// Use Chain Gun + +//------------------------------------------------------------ +qboolean B_UseChainGun(edict_t *ent,edict_t *target,int enewep,float aim,float distance,int skill) +{ + int k,mywep; + zgcl_t *zc; + gclient_t *client; + + client = ent->client; + zc = &client->zc; + + if(k = CanUsewep(ent,WEAP_CHAINGUN)) + { + mywep = Get_KindWeapon(client->pers.weapon); + Get_AimAngle(ent,aim,distance,mywep); + client->buttons |= BUTTON_ATTACK; + if(trace_priority < TRP_ANGLEKEEP) trace_priority = TRP_ANGLEKEEP; + return true; + } + return false; +} + + +//------------------------------------------------------------ + +// Use Machine Gun + +//------------------------------------------------------------ +qboolean B_UseMachineGun(edict_t *ent,edict_t *target,int enewep,float aim,float distance,int skill) +{ + int k,mywep; + zgcl_t *zc; + gclient_t *client; + + client = ent->client; + zc = &client->zc; + + if(k = CanUsewep(ent,WEAP_MACHINEGUN)) + { + mywep = Get_KindWeapon(client->pers.weapon); + Get_AimAngle(ent,aim,distance,mywep); + if(k == true) client->buttons |= BUTTON_ATTACK; + if(trace_priority < TRP_ANGLEKEEP) trace_priority = TRP_ANGLEKEEP; + return true; + } + return false; +} + +//------------------------------------------------------------ + +// Use S-Shotgun + +//------------------------------------------------------------ +qboolean B_UseSuperShotgun(edict_t *ent,edict_t *target,int enewep,float aim,float distance,int skill) +{ + int k,mywep; + zgcl_t *zc; + gclient_t *client; + + client = ent->client; + zc = &client->zc; + + if(k = CanUsewep(ent,WEAP_SUPERSHOTGUN)) + { + mywep = Get_KindWeapon(client->pers.weapon); + Get_AimAngle(ent,aim,distance,mywep); + client->buttons |= BUTTON_ATTACK; + if(trace_priority < TRP_ANGLEKEEP) trace_priority = TRP_ANGLEKEEP; + return true; + } + return false; +} + +//------------------------------------------------------------ + +// Use Shotgun + +//------------------------------------------------------------ +qboolean B_UseShotgun(edict_t *ent,edict_t *target,int enewep,float aim,float distance,int skill) +{ + int k,mywep; + zgcl_t *zc; + gclient_t *client; + + client = ent->client; + zc = &client->zc; + + if(k = CanUsewep(ent,WEAP_SHOTGUN)) + { + mywep = Get_KindWeapon(client->pers.weapon); + Get_AimAngle(ent,aim,distance,mywep); + client->buttons |= BUTTON_ATTACK; + if(trace_priority < TRP_ANGLEKEEP) trace_priority = TRP_ANGLEKEEP; + return true; + } + return false; +} + +//------------------------------------------------------------ + +// Use Hand Grenade + +//------------------------------------------------------------ +qboolean B_UseHandGrenade(edict_t *ent,edict_t *target,int enewep,float aim,float distance,int skill) +{ + int k,mywep; + zgcl_t *zc; + gclient_t *client; + + client = ent->client; + zc = &client->zc; + + if(k = CanUsewep(ent,WEAP_GRENADES)) + { + mywep = Get_KindWeapon(client->pers.weapon); + Get_AimAngle(ent,aim,distance,mywep); + if(ent->client->weaponstate == WEAPON_READY ) client->buttons |= BUTTON_ATTACK; + if(trace_priority < TRP_ANGLEKEEP) trace_priority = TRP_ANGLEKEEP; + return true; + } + return false; +} + +//------------------------------------------------------------ + +// Use Trap + +//------------------------------------------------------------ +qboolean B_UseTrap(edict_t *ent,edict_t *target,int enewep,float aim,float distance,int skill) +{ + int k,mywep; + zgcl_t *zc; + gclient_t *client; + + client = ent->client; + zc = &client->zc; + + if(k = CanUsewep(ent,WEAP_TRAP)) + { + mywep = Get_KindWeapon(client->pers.weapon); + Get_AimAngle(ent,aim,distance,mywep); + if(ent->client->weaponstate == WEAPON_READY ) client->buttons |= BUTTON_ATTACK; + if(trace_priority < TRP_ANGLEKEEP) trace_priority = TRP_ANGLEKEEP; + return true; + } + return false; +} + + +//------------------------------------------------------------ + +// Use Blaster + +//------------------------------------------------------------ +qboolean B_UseBlaster(edict_t *ent,edict_t *target,int enewep,float aim,float distance,int skill) +{ + int k,mywep; + zgcl_t *zc; + gclient_t *client; + + client = ent->client; + zc = &client->zc; + + if(k = CanUsewep(ent,WEAP_BLASTER)) + { + mywep = Get_KindWeapon(client->pers.weapon); + Get_AimAngle(ent,aim,distance,mywep); + client->buttons |= BUTTON_ATTACK; + if(trace_priority < TRP_ANGLEKEEP) trace_priority = TRP_ANGLEKEEP; + return true;; + } + return false; +} + +//return weapon +void Combat_LevelX(edict_t *ent,int foundedenemy,int enewep + ,float aim,float distance,int skill) +{ + gclient_t *client; + zgcl_t *zc; + edict_t *target; + int mywep,k; + vec3_t v; + qboolean mod = false; + + client = ent->client; + zc = &client->zc; + target = zc->first_target; + + //----------------------------------------------------------------------- + //ステータスを反映 + //----------------------------------------------------------------------- + k = false; + //予測======================== + if(zc->battlemode & FIRE_ESTIMATE) + { + mywep = Get_KindWeapon(client->pers.weapon); + //Phalanx + if(distance > 100 || mywep == WEAP_PHALANX) + { + if(B_UsePhalanx(ent,target,enewep,aim,distance,skill)) k = true; + } + + //Rocket + if(distance > 100 || mywep == WEAP_ROCKETLAUNCHER) + { + if(B_UseRocket(ent,target,enewep,aim,distance,skill)) k = true; + } + + //Boomer + if(distance < 1200) + { + if(B_UseBoomer(ent,target,enewep,aim,distance,skill)) k = true; + } + //Grenade Launcher + if(distance > 100 && distance < 400 && (target->s.origin[2] - ent->s.origin[2]) < 200) + { + if(B_UseGrenadeLauncher(ent,target,enewep,aim,distance,skill)) k = true; + } + //Hand Grenade + if(distance < 1200) + { + if(B_UseHandGrenade(ent,target,enewep,aim,distance,skill)) k = true; + } + VectorSubtract(zc->vtemp,ent->s.origin,v); + ent->s.angles[YAW] = Get_yaw(v); + ent->s.angles[PITCH] = Get_pitch(v); + if(k) trace_priority = TRP_ALLKEEP; + else trace_priority = TRP_ANGLEKEEP; + return; + } + VectorSubtract(target->s.origin,ent->s.origin,v); + ent->s.angles[YAW] = Get_yaw(v); + ent->s.angles[PITCH] = Get_pitch(v); + trace_priority = TRP_ANGLEKEEP; +} + +//return weapon +void Combat_Level0(edict_t *ent,int foundedenemy,int enewep + ,float aim,float distance,int skill) +{ + float f; + gclient_t *client; + zgcl_t *zc; + + edict_t *target; + int mywep,i,j,k; + vec3_t v,vv,v1,v2; + qboolean mod = false; + + trace_t rs_trace; + + client = ent->client; + zc = &client->zc; + target = zc->first_target; + + + //----------------------------------------------------------------------- + //ステータスを反映 + //----------------------------------------------------------------------- + //チキンは狙いがキツイ============== + if(zc->battlemode == FIRE_CHIKEN) aim *= 0.7; + //左右に回避======================== + if(zc->battlemode & FIRE_SHIFT) + { + mywep = Get_KindWeapon(client->pers.weapon); + Get_AimAngle(ent,aim,distance,mywep); + + if(--zc->battlesubcnt > 0) + { + if(ent->groundentity) + { + if(zc->battlemode & FIRE_SHIFT_R) + { + zc->moveyaw = ent->s.angles[YAW] + 90; + if(zc->moveyaw > 180) zc->moveyaw -= 360; + } + else + { + zc->moveyaw = ent->s.angles[YAW] - 90; + if(zc->moveyaw < -180) zc->moveyaw += 360; + } + trace_priority = TRP_MOVEKEEP; //後退処理 + } + } + else + { + zc->battlemode &= ~FIRE_SHIFT; + } + } + + //dodge============================= + if(Bot[ent->client->zc.botindex].param[BOP_DODGE] + && ent->groundentity && !ent->waterlevel) + { + AngleVectors (target->client->v_angle, v,NULL, NULL); + VectorScale (v, 300, v); + + VectorSet(vv, 0, 0, target->viewheight-8); + VectorAdd(target->s.origin,vv,vv); + VectorAdd(vv,v,v); + + VectorSet(v1, -4, -4,-4); + VectorSet(v2, 4, 4, 4); + rs_trace = gi.trace(vv,v1,v2,v,target,MASK_SHOT); + + if(rs_trace.ent == ent) + { + if(rs_trace.endpos[2] > (ent->s.origin[2] + 4) && random() < 0.4) + { + client->ps.pmove.pm_flags |= PMF_DUCKED; + zc->battleduckcnt = 2 + 8 * random(); + } + else if(rs_trace.endpos[2] < (ent->s.origin[2] + JumpMax - 24)) + { + if(zc->route_trace) + { + if(Bot_Fall(ent,ent->s.origin,0)) trace_priority = TRP_MOVEKEEP;; + } + else + { + ent->moveinfo.speed = 0.5; + + ent->velocity[2] += VEL_BOT_JUMP; + gi.sound(ent, CHAN_VOICE, gi.soundindex("*jump1.wav"), 1, ATTN_NORM, 0); + PlayerNoise(ent, ent->s.origin, PNOISE_SELF); //pon + Set_BotAnim(ent,ANIM_JUMP,FRAME_jump1-1,FRAME_jump6); + } + } + } + } + //無視して走る======================== + if(zc->battlemode & FIRE_IGNORE) + { + if(--zc->battlecount > 0) + { + if(zc->first_target != zc->last_target) + { + zc->battlemode = 0; + } + else return; + } + zc->battlemode = 0; + } + + //立ち止まって撃つ準備======================== + if(zc->battlemode & FIRE_PRESTAYFIRE) + { + if(--zc->battlecount > 0) + { + mywep = Get_KindWeapon(client->pers.weapon); + Get_AimAngle(ent,aim,distance,mywep); + if(target->client->weaponstate == WEAPON_FIRING && ent->groundentity) ent->client->ps.pmove.pm_flags |= PMF_DUCKED; + trace_priority = TRP_ALLKEEP; //動かない + return; + } + if(!(zc->battlemode & FIRE_SHIFT)) zc->battlemode = FIRE_STAYFIRE; //モード遷移 + zc->battlecount = 5 + (int)(20 * random()); + } + + //立ち止まって撃つ======================== + if(zc->battlemode & FIRE_STAYFIRE) + { + if(--zc->battlecount > 0) + { + mywep = Get_KindWeapon(client->pers.weapon); + if(1/*mywep == WEAP_BFG*/) CanUsewep(ent,WEAP_BFG); + aim *= 0.95; + Get_AimAngle(ent,aim,distance,mywep); + if(target->client->weaponstate == WEAPON_FIRING && ent->groundentity) + { + if(mywep == WEAP_BFG) + { + if(target->s.origin[2] > ent->s.origin[2]) client->ps.pmove.pm_flags |= PMF_DUCKED; + } + else client->ps.pmove.pm_flags |= PMF_DUCKED; + } + if(!(zc->battlemode & FIRE_SHIFT)) trace_priority = TRP_ALLKEEP; //動かない + if(Bot_traceS(ent,target) + || mywep == WEAP_BFG + || mywep == WEAP_GRENADELAUNCHER) client->buttons |= BUTTON_ATTACK; + return; + } + zc->battlemode = 0; + } + + //FIRE_RUSH つっこむ======================== + if(zc->battlemode & FIRE_RUSH) + { + if(--zc->battlecount > 0) + { + mywep = Get_KindWeapon(client->pers.weapon); + if(1/*mywep == WEAP_BFG*/) CanUsewep(ent,WEAP_BFG); + aim *= 0.95; + Get_AimAngle(ent,aim,distance,mywep); + if(target->client->weaponstate == WEAPON_FIRING && ent->groundentity) + { + if(mywep == WEAP_BFG) + { + if(target->s.origin[2] > ent->s.origin[2]) client->ps.pmove.pm_flags |= PMF_DUCKED; + } + else client->ps.pmove.pm_flags |= PMF_DUCKED; + } + trace_priority = TRP_MOVEKEEP; //後退処理 + zc->moveyaw = ent->s.angles[YAW]; + + if(Bot_traceS(ent,target)) client->buttons |= BUTTON_ATTACK; + return; + } + zc->battlemode = 0; + } + + //後退ファイア(爆発回避)======================== + if(zc->battlemode & FIRE_EXPAVOID) + { + if(--zc->battlecount > 0) + { + mywep = Get_KindWeapon(client->pers.weapon); + if(1/*mywep == WEAP_BFG*/) CanUsewep(ent,WEAP_BFG); + aim *= 0.95; + Get_AimAngle(ent,aim,distance,mywep); + if(target->client->weaponstate == WEAPON_FIRING && ent->groundentity) + { + if(mywep == WEAP_BFG) + { + if(target->s.origin[2] > ent->s.origin[2]) client->ps.pmove.pm_flags |= PMF_DUCKED; + } + else client->ps.pmove.pm_flags |= PMF_DUCKED; + } + trace_priority = TRP_MOVEKEEP; //後退処理 + zc->moveyaw = ent->s.angles[YAW] + 180; + if(zc->moveyaw > 180) zc->moveyaw -= 360; + + if(Bot_traceS(ent,target) + || mywep == WEAP_BFG + || mywep == WEAP_GRENADELAUNCHER) client->buttons |= BUTTON_ATTACK; + return; + } + zc->battlemode = 0; + } + //BFGファイア(爆発回避)======================== + if(zc->battlemode & FIRE_BFG) + { + if(--zc->battlecount > 0) + { + mywep = Get_KindWeapon(client->pers.weapon); + if(1/*mywep == WEAP_BFG*/) CanUsewep(ent,WEAP_BFG); + aim *= 0.95; + Get_AimAngle(ent,aim,distance,mywep); + if(target->client->weaponstate == WEAPON_FIRING && ent->groundentity) + { + if(1/*mywep == WEAP_BFG*/) + { + if(target->s.origin[2] > ent->s.origin[2]) client->ps.pmove.pm_flags |= PMF_DUCKED; + } + else client->ps.pmove.pm_flags |= PMF_DUCKED; + } + trace_priority = TRP_ANGLEKEEP; //後退処理 + + if(Bot_traceS(ent,target) + || mywep == WEAP_BFG + || mywep == WEAP_GRENADELAUNCHER) client->buttons |= BUTTON_ATTACK; + return; + } + zc->battlemode = 0; + } + + //撃って避難======================== + if(zc->battlemode & FIRE_REFUGE) + { + if(--zc->battlecount > 0) + { + mywep = Get_KindWeapon(client->pers.weapon); + //CanUsewep(ent,WEAP_BFG); + aim *= 0.95; + Get_AimAngle(ent,aim,distance,mywep); + if(target->client->weaponstate == WEAPON_FIRING && ent->groundentity) + { + if(mywep == WEAP_BFG) + { + if(target->s.origin[2] > ent->s.origin[2]) client->ps.pmove.pm_flags |= PMF_DUCKED; + } + else client->ps.pmove.pm_flags |= PMF_DUCKED; + } + trace_priority = TRP_ANGLEKEEP; //動かない +// trace_priority = TRP_ALLKEEP; //動かない + if(Bot_traceS(ent,target) + || mywep == WEAP_BFG + || mywep == WEAP_GRENADELAUNCHER) client->buttons |= BUTTON_ATTACK; + return; + } + zc->battlemode = 0; + zc->routeindex -= 2; + } + + if(!(client->zc.zccmbstt & CTS_ENEM_NSEE) + && (zc->zcstate & STS_WAITSMASK2) + && (target->s.origin[2] - ent->s.origin[2]) < -300) + { + if(k = CanUsewep(ent,WEAP_GRENADELAUNCHER)) + { + mywep = Get_KindWeapon(client->pers.weapon); + Get_AimAngle(ent,aim,distance,mywep); + if((target->client->weaponstate == WEAPON_FIRING && ent->groundentity) || (zc->zcstate & STS_WAITSMASK2)) ent->client->ps.pmove.pm_flags |= PMF_DUCKED; + client->buttons |= BUTTON_ATTACK; + trace_priority = TRP_ANGLEKEEP; + return; + } + if(k = CanUsewep(ent,WEAP_GRENADES)) + { + mywep = Get_KindWeapon(client->pers.weapon); + Get_AimAngle(ent,aim,distance,mywep); + if(target->client->weaponstate == WEAPON_FIRING && ent->groundentity) ent->client->ps.pmove.pm_flags |= PMF_DUCKED; + if(ent->client->weaponstate == WEAPON_READY ) client->buttons |= BUTTON_ATTACK; + trace_priority = TRP_ANGLEKEEP; + return; + } + } + + //----------------------------------------------------------------------- + //特殊ファイアリング + //----------------------------------------------------------------------- + mywep = Get_KindWeapon(client->pers.weapon); + + //左右回避セット======================== + if(!(zc->battlemode & FIRE_SHIFT) && skill > (random() * skill) /*&& distance < 250*/ + && (30 * random()) < Bot[zc->botindex].param[BOP_OFFENCE]) + { + k = false; + if(zc->route_trace && enewep != WEAP_RAILGUN) + { + for(i = zc->routeindex;i < (zc->routeindex + 10);i++) + { + if(i >= CurrentIndex) break; + if(Route[i].state == GRS_ITEMS) + { + if(Route[i].ent->solid == SOLID_TRIGGER) + { + k = true; + break; + } + } + } + } + if(!k) + { + Get_AimAngle(ent,aim,distance,mywep); + f =target->s.angles[YAW] - ent->s.angles[YAW]; + + if(f > 180) + { + f = -(360 - f); + } + if( f < -180) + { + f = -(f + 360); + } + + //俺をみている!! + if(f <= -160) + { + + zc->battlemode |= FIRE_SHIFT_L; + zc->battlesubcnt = 5 + (int)(16 * random()); + } + else if(f >= 160) + { + zc->battlemode |= FIRE_SHIFT_R; + zc->battlesubcnt = 5 + (int)(16 * random()); + } + } + } + + //敵がペンタをとっている======================== + if((FFlg[skill] & FIRE_AVOIDINV) + && target->client->invincible_framenum > level.framenum) + { +// mywep = Get_KindWeapon(client->pers.weapon); + Get_AimAngle(ent,aim,distance,mywep); + trace_priority = TRP_MOVEKEEP; //後退処理 + zc->moveyaw = ent->s.angles[YAW] + 180; + if(zc->moveyaw > 180) zc->moveyaw -= 360; + return; + } + //Quad時の処理================================= + if((FFlg[skill] & FIRE_QUADUSE) + && (ent->client->quad_framenum > level.framenum) + && distance < 300) + { + j = false; + if(enewep < WEAP_MACHINEGUN || enewep == WEAP_GRENADES) j = true; + + //Hyper Blaster + if(k = CanUsewep(ent,WEAP_HYPERBLASTER)) + { + mywep = Get_KindWeapon(client->pers.weapon); + Get_AimAngle(ent,aim,distance,mywep); + client->buttons |= BUTTON_ATTACK; + trace_priority = TRP_ANGLEKEEP; + if(j) + { + zc->battlemode |= FIRE_RUSH; + zc->battlecount = 8 + (int)(10 * random()); + } + return; + } + //Chain Gun + if(k = CanUsewep(ent,WEAP_CHAINGUN)) + { + mywep = Get_KindWeapon(client->pers.weapon); + Get_AimAngle(ent,aim,distance,mywep); + client->buttons |= BUTTON_ATTACK; + trace_priority = TRP_ANGLEKEEP; + if(j) + { + zc->battlemode |= FIRE_RUSH; + zc->battlecount = 8 + (int)(10 * random()); + } + return; + } + //Machine Gun + if(k = CanUsewep(ent,WEAP_MACHINEGUN)) + { + mywep = Get_KindWeapon(client->pers.weapon); + Get_AimAngle(ent,aim,distance,mywep); + client->buttons |= BUTTON_ATTACK; + trace_priority = TRP_ANGLEKEEP; + if(j) + { + zc->battlemode |= FIRE_RUSH; + zc->battlecount = 8 + (int)(10 * random()); + } + return; + } + //S-Shotgun + if(k = CanUsewep(ent,WEAP_SUPERSHOTGUN)) + { + mywep = Get_KindWeapon(client->pers.weapon); + Get_AimAngle(ent,aim,distance,mywep); + client->buttons |= BUTTON_ATTACK; + trace_priority = TRP_ANGLEKEEP; + if(j) + { + zc->battlemode |= FIRE_RUSH; + zc->battlecount = 8 + (int)(10 * random()); + } + return; + } + } + //撃って逃げる処理================================= + if((FFlg[skill] & FIRE_REFUGE) + && zc->battlemode == 0 && zc->route_trace && zc->routeindex > 1 ) + { + j = false; + if(enewep >= WEAP_CHAINGUN && enewep != WEAP_GRENADES) j = true; + + + Get_RouteOrigin(zc->routeindex - 2,v); + + if(fabs(v[2] - ent->s.origin[2]) < JumpMax && j) + { + mywep = Get_KindWeapon(client->pers.weapon); + if(mywep == WEAP_GRENADELAUNCHER + || mywep == WEAP_ROCKETLAUNCHER + || mywep == WEAP_PHALANX) + { + zc->battlemode |= FIRE_REFUGE; //モード遷移 + zc->battlecount = 8 + (int)(10 * random()); + trace_priority = TRP_ALLKEEP; + return; + } + } + } + //トレース中以外のときにグルグルを防ぐ================================= + if(!zc->route_trace && distance < 100) + { + zc->battlemode |= FIRE_EXPAVOID; //モード遷移 + zc->battlecount = 4 + (int)(8 * random()); + trace_priority = TRP_ALLKEEP; + } + + + + //----------------------------------------------------------------------- + //プライオリティ + //----------------------------------------------------------------------- + //BFG + if(distance > 200) + { + if(B_UseBfg(ent,target,enewep,aim,distance,skill)) goto FIRED; + } + + for(i = 0;i < 3;i++) + { + mywep = Get_KindWeapon(client->pers.weapon); + + if(i == 0 && zc->secwep_selected) continue; + + //try to select secondary weapon + if(i == 0 && zc->secwep_selected) i = 1; + else if(i == 0 && foundedenemy < 3 + && target->health < 50 && !zc->secwep_selected + && ent->health >= 50) + { + if((9 * random()) < Bot[zc->botindex].param[BOP_COMBATSKILL]) + { + zc->secwep_selected = 2; + i = 1; + } + } + + if(i == 2) + { + if(zc->secwep_selected) + { + zc->secwep_selected = 0; + j = 0; + } + else break; + } + else j = i; + + if(distance > 100 && (mywep == WEAP_BFG || random() < 0.5)) + { + if(B_UseBfg(ent,target,enewep,aim,distance,skill)) goto FIRED; + } + + switch(Bot[zc->botindex].param[BOP_PRIWEP + j]) + { + case WEAP_BFG: + if(distance > 100) + { + if(B_UseBfg(ent,target,enewep,aim,distance,skill)) goto FIRED; + } + break; + case WEAP_HYPERBLASTER: + if(distance < 1200) + { + if(B_UseHyperBlaster(ent,target,enewep,aim,distance,skill)) goto FIRED; + } + break; + case WEAP_PHALANX: + if(distance > 100 && distance < 1200/*|| mywep == WEAP_PHALANX*/) + { + if(B_UsePhalanx(ent,target,enewep,aim,distance,skill)) goto FIRED; + } + break; + case WEAP_ROCKETLAUNCHER: + if(distance > 100 && distance < 1200/*|| mywep == WEAP_ROCKETLAUNCHER*/) + { + if(B_UseRocket(ent,target,enewep,aim,distance,skill)) goto FIRED; + } + break; + case WEAP_BOOMER: + if(distance < 1200) + { + if(B_UseBoomer(ent,target,enewep,aim,distance,skill)) goto FIRED; + } + break; + case WEAP_RAILGUN: + if(distance < 1200) + { + if(B_UseRailgun(ent,target,enewep,aim,distance,skill)) goto FIRED; + } + break; + case WEAP_GRENADELAUNCHER: + if(distance > 100 && distance < 400 && (target->s.origin[2] - ent->s.origin[2]) < 200) + { + if(B_UseGrenadeLauncher(ent,target,enewep,aim,distance,skill)) goto FIRED; + } + break; + case WEAP_CHAINGUN: + case WEAP_MACHINEGUN: + if(distance < 1200) + { + if(B_UseChainGun(ent,target,enewep,aim,distance,skill)) goto FIRED; + } + if(distance < 1200) + { + if(B_UseMachineGun(ent,target,enewep,aim,distance,skill)) goto FIRED; + } + break; + case WEAP_SUPERSHOTGUN: + case WEAP_SHOTGUN: + if(distance < 1200) + { + if(B_UseSuperShotgun(ent,target,enewep,aim,distance,skill)) goto FIRED; + } + if(distance < 1200) + { + if(B_UseShotgun(ent,target,enewep,aim,distance,skill)) goto FIRED; + } + case WEAP_GRENADES: + if(distance < 1200) + { + if(B_UseHandGrenade(ent,target,enewep,aim,distance,skill)) goto FIRED; + } + break; + default: + break; + } + } + + + //----------------------------------------------------------------------- + //通常ファイアリング + //----------------------------------------------------------------------- + zc->secwep_selected = 0; + //BFG + if(distance > 200) + { + if(B_UseBfg(ent,target,enewep,aim,distance,skill)) goto FIRED; + } + + //Hyper Blaster + if(distance < 1200) + { + if(B_UseHyperBlaster(ent,target,enewep,aim,distance,skill)) goto FIRED; + } + + //Phalanx + if((distance > 100 && distance < 1200)/*|| mywep == WEAP_PHALANX*/) + { + if(B_UsePhalanx(ent,target,enewep,aim,distance,skill)) goto FIRED; + } + + //Rocket + if((distance > 100 && distance < 1200)/*|| mywep == WEAP_ROCKETLAUNCHER*/) + { + if(B_UseRocket(ent,target,enewep,aim,distance,skill)) goto FIRED; + } + + //Boomer + if(distance < 1200) + { + if(B_UseBoomer(ent,target,enewep,aim,distance,skill)) goto FIRED; + } + + //Railgun + if(distance < 1200) + { + if(B_UseRailgun(ent,target,enewep,aim,distance,skill)) goto FIRED; + } + + //Grenade Launcher + if(distance > 100 && distance < 400 && (target->s.origin[2] - ent->s.origin[2]) < 200) + { + if(B_UseGrenadeLauncher(ent,target,enewep,aim,distance,skill)) goto FIRED; + } + //Chain Gun + if(distance < 1200) + { + if(B_UseChainGun(ent,target,enewep,aim,distance,skill)) goto FIRED; + } + //Machine Gun + if(distance < 1200) + { + if(B_UseMachineGun(ent,target,enewep,aim,distance,skill)) goto FIRED; + } + //S-Shotgun + if(distance < 1200) + { + if(B_UseSuperShotgun(ent,target,enewep,aim,distance,skill)) goto FIRED; + } + + if((FFlg[skill] & FIRE_IGNORE) + && distance > 400 && ent->groundentity + && !(zc->zcstate & STS_WAITSMASK)) + { + zc->battlemode = FIRE_IGNORE; + zc->battlecount = 5 + (int)(10 * random()); + + } + + //Shotgun + if(distance < 1200) + { + if(B_UseShotgun(ent,target,enewep,aim,distance,skill)) goto FIRED; + } + //Hand Grenade + if(distance < 400) + { + if(B_UseHandGrenade(ent,target,enewep,aim,distance,skill)) goto FIRED; + } + //Trap + if(distance < 400) + { + if(B_UseTrap(ent,target,enewep,aim,distance,skill)) goto FIRED; + } + //Blaster + if(distance < 1200) + { + if(B_UseBlaster(ent,target,enewep,aim,distance,skill)) goto FIRED; + } + + VectorSubtract(zc->vtemp,ent->s.origin,v); + ent->s.angles[YAW] = Get_yaw(v); + ent->s.angles[PITCH] = Get_pitch(v); + trace_priority = TRP_ANGLEKEEP; + return; + +FIRED: + if(zc->secwep_selected == 2) zc->secwep_selected = 1; + + //チキンやろう======================== + if(zc->battlemode == FIRE_CHIKEN) + { + if(--zc->battlesubcnt > 0 && ent->groundentity && ent->waterlevel < 2) + { + f =target->s.angles[YAW] - ent->s.angles[YAW]; + + if(f > 180) + { + f = -(360 - f); + } + if( f < -180) + { + f = -(f + 360); + } + if(fabs(f) >= 150) + { + zc->battlemode = 0; + } + else + { + if(client->weaponstate != WEAPON_READY + && target->s.origin[2] < ent->s.origin[2] ) + { + if(mywep == WEAP_ROCKETLAUNCHER + || mywep == WEAP_PHALANX + || mywep == WEAP_GRENADELAUNCHER + || mywep == WEAP_RAILGUN) + client->ps.pmove.pm_flags |= PMF_DUCKED; + else if(Bot[zc->botindex].param[BOP_COMBATSKILL] >= 7) + { + if(mywep == WEAP_SHOTGUN + || mywep == WEAP_SUPERSHOTGUN + || mywep == WEAP_BLASTER) + client->ps.pmove.pm_flags |= PMF_DUCKED; + } + } + trace_priority = TRP_ALLKEEP; + } + return; + } + else zc->battlemode = 0; + } + else if(zc->battlemode == 0 && distance > 200 + && ent->groundentity && ent->waterlevel < 2 + && (9 * random()) > Bot[zc->botindex].param[BOP_OFFENCE]) + { + mywep = Get_KindWeapon(client->pers.weapon); + if(mywep > WEAP_BLASTER && target->client->zc.first_target != ent) + { + f =target->s.angles[YAW] - ent->s.angles[YAW]; + + if(f > 180) + { + f = -(360 - f); + } + if( f < -180) + { + f = -(f + 360); + } + if(fabs(f) < 150) + { + zc->battlemode = FIRE_CHIKEN; + zc->battlesubcnt = 5 + (int)(random() * 8); + trace_priority = TRP_ALLKEEP; + } + } + } +} + + + +void UsePrimaryWeapon(edict_t *ent) +{ + int mywep = Get_KindWeapon(ent->client->pers.weapon); + + if(CanUsewep(ent,WEAP_BFG)) return; + + CanUsewep(ent,Bot[ent->client->zc.botindex].param[BOP_PRIWEP]); +} + + + +/*------------------------------------------------------------------------------*/ + +void UpdateExplIndex(edict_t* ent) +{ + int i; + qboolean mod = false; + + for(i = 0;i < MAX_EXPLINDEX;i++) + { + if(ExplIndex[i] != NULL) {if(ExplIndex[i]->inuse == false) ExplIndex[i] = NULL;} + if(!mod && ExplIndex[i] == NULL) {ExplIndex[i] = ent;mod = true;} + } +} + diff --git a/src/bot_func.c b/src/bot_func.c new file mode 100644 index 0000000..34c6f56 --- /dev/null +++ b/src/bot_func.c @@ -0,0 +1,1079 @@ +#include "bot.h" +#include "m_player.h" + + +qboolean Get_YenPos(char *Buff,int *curr) +{ + int i; + + i = *curr + 1; + + while(1) + { +// if(i >= strlen(Buff)) return false; + if(Buff[i] == 0 || Buff[i] == 10 || Buff[i] == 13) + { + *curr = i; + return true; + } + if(Buff[i] == '\\') + { + *curr = i; + return true; + } + if(Buff[i] == '\t') Buff[i] = 0; + i++; + } +} +//---------------------------------------------------------------- +//Load Bot Info +// +// Load bot's infomation from 3ZBConfig.cfg +// +//---------------------------------------------------------------- +void Load_BotInfo() +{ + char MessageSection[50]; + char Buff[1024]; + int i,j,k,l; + + FILE *fp; + + SpawnWaitingBots = 0; + ListedBotCount = 0; + + //init message + memset(ClientMessage,0,sizeof(ClientMessage)); + //set message section + if(!ctf->value && chedit->value) strcpy(MessageSection,MESS_CHAIN_DM); + else if(ctf->value && !chedit->value) strcpy(MessageSection,MESS_CTF); + else if(ctf->value && chedit->value) strcpy(MessageSection,MESS_CHAIN_CTF); + else strcpy(MessageSection,MESS_DEATHMATCH); + + //init botlist + ListedBots = 0; + j = 1; + for(i = 0;i < MAXBOTS;i++) + { + //netname + sprintf(Buff,"Zigock[%i]",i); + strcpy(Bot[i].netname,Buff); + //model + strcpy(Bot[i].model,"male"); + //skin + strcpy(Bot[i].model,"grunt"); + + //param + Bot[i].param[BOP_WALK] = 0; + Bot[i].param[BOP_AIM] = 5; + Bot[i].param[BOP_PICKUP] = 5; + Bot[i].param[BOP_COMBATSKILL] = 5; + Bot[i].param[BOP_ROCJ] = 0; + Bot[i].param[BOP_VRANGE] = 90; + Bot[i].param[BOP_HRANGE] = 180; + Bot[i].param[BOP_REACTION] = 0; + + //spawn flag + Bot[i].spflg = 0; + //team + Bot[i].team = j; + if(++j > 2) j = 1; + } + + //botlist value + botlist = gi.cvar ("botlist", "default", CVAR_SERVERINFO | CVAR_LATCH); + gamepath = gi.cvar ("game", "0", CVAR_NOSET); + + //load info + sprintf(Buff,".\\%s\\3ZBconfig.cfg",gamepath->string); + fp = fopen(Buff,"rt"); + if(fp == NULL) + { + gi.dprintf("3ZB CFG: file not found.\n"); + return; + } + else + { + fseek( fp, 0, SEEK_SET); //先頭へ移動 + while(1) + { + if(fgets( Buff, sizeof(Buff), fp ) == NULL) goto MESS_NOTFOUND; + if(!_strnicmp(MessageSection,Buff,strlen(MessageSection))) break; + } + + while(1) + { + if(fgets( Buff, sizeof(Buff), fp ) == NULL) goto MESS_NOTFOUND; + if(Buff[0] == '.' || Buff[0] == '[' || Buff[0] == '#') break; + k = strlen(Buff); + if((strlen(Buff) + strlen(ClientMessage)) > MAX_STRING_CHARS - 1) break; + strcat(ClientMessage,Buff); + } +MESS_NOTFOUND: + //if(botlist->string == NULL) strcpy(MessageSection,BOTLIST_SECTION_DM); + //else + sprintf(MessageSection,"[%s]",botlist->string); + fseek( fp, 0, SEEK_SET); //先頭へ移動 + while(1) + { + if(fgets( Buff, sizeof(Buff), fp ) == NULL) + { + MessageSection[0] = 0; + break; + } + if(!_strnicmp(MessageSection,Buff,strlen(MessageSection))) break; + } + //when not found + if(MessageSection[0] == 0) + { + strcpy(MessageSection,BOTLIST_SECTION_DM); + fseek( fp, 0, SEEK_SET); //先頭へ移動 + while(1) + { + if(fgets( Buff, sizeof(Buff), fp ) == NULL) goto BOTLIST_NOTFOUND; + if(!_strnicmp(MessageSection,Buff,strlen(MessageSection))) break; + } + } + + i = 0; + for(i = 0;i < MAXBOTS;i++) + { + if(fgets( Buff, sizeof(Buff), fp ) == NULL) break; + if(Buff[0] == '[') break; + if(Buff[0] == '\n' || Buff[0] == '#') {i--;continue;} + j = 2,k = 1; + if(!strncmp(Buff,"\\\\",2)) + { + //netname + if(Get_YenPos(Buff,&k)) + { + Buff[k] = 0; + if(strlen(&Buff[j]) < 21) strcpy(Bot[i].netname,&Buff[j]); + j = k + 1; + } + else break; + //model name + if(Get_YenPos(Buff,&k)) + { + Buff[k] = 0; + if(strlen(&Buff[j]) < 21) strcpy(Bot[i].model,&Buff[j]); + j = k + 1; + k++; + } + else break; + //skin name + if(Get_YenPos(Buff,&k)) + { + Buff[k] = 0; + if(strlen(&Buff[j]) < 21) strcpy(Bot[i].skin,&Buff[j]); + j = k + 1; + k++; + } + else break; + for(l = 0;l < MAXBOP;l++) + { + //param0-7 + if(Get_YenPos(Buff,&k)) + { + Buff[k] = 0; + Bot[i].param[l] = (unsigned char)atoi(&Buff[j]); + j = k + 1; + k++; + } + else break; + } + if(l < MAXBOP) break; + //team + if(Get_YenPos(Buff,&k)) + { + Buff[k] = 0; + if(Buff[j] == 'R') Bot[i].team = 1; + else if(Buff[j] == 'B') Bot[i].team = 2; + else Bot[i].team = 1; + j = k + 1; + k++; + } + else break; + //auto spawn + if(Get_YenPos(Buff,&k)) + { + Buff[k] = 0; + Bot[i].spflg = atoi(&Buff[j]); +//gi.dprintf("%i %s\n",Bot[i].spflg,&Buff[j]); + if(Bot[i].spflg == BOT_SPRESERVED && autospawn->value && !chedit->value) SpawnWaitingBots++; + else Bot[i].spflg = BOT_SPAWNNOT; + } + else break; + ListedBots++; + } + } + } +BOTLIST_NOTFOUND: + fclose(fp); + + gi.dprintf("%i of Bots is listed.\n",ListedBots); + spawncycle = level.time + FRAMETIME * 100; +} + +//---------------------------------------------------------------- +//Get Number of Client +// +// Total Client +// +//---------------------------------------------------------------- + +int Get_NumOfPlayer (void) //Botも含めたplayerの数 +{ + int i,j; + edict_t *ent; + + j = 0; + for (i=0 ; ivalue ; i++) + { + ent = g_edicts + 1 + i; + if (ent->inuse) j++; + } + return j; +} + +//---------------------------------------------------------------- +//Get New Client +// +// Get new client edict +// +//---------------------------------------------------------------- + +edict_t *Get_NewClient (void) +{ + int i; + edict_t *e; + gclient_t *client; + + e = &g_edicts[(int)maxclients->value]; + for ( i = maxclients->value ; i >= 1 ; i--, e--) + { + client = &game.clients[i - 1]; + // the first couple seconds of server time can involve a lot of + // freeing and allocating, so relax the replacement policy + if (!e->inuse && !client->pers.connected && ( e->freetime < 2 || level.time - e->freetime > 0.5 ) ) + { + G_InitEdict (e); + return e; + } + } + gi.error ("ED_Alloc: no free edicts shit"); + return NULL; +} + + +//---------------------------------------------------------------- +//Bot Think +// +// Bot's think code +// +//---------------------------------------------------------------- +void Bot_Think (edict_t *self) +{ + gclient_t *client; + + if (self->linkcount != self->monsterinfo.linkcount) + { +// self->monsterinfo.linkcount = self->linkcount; + M_CheckGround (self); + } + + if(self->deadflag) + { + if(self->client->ctf_grapple) CTFPlayerResetGrapple(self); + + if(self->s.modelindex == skullindex || self->s.modelindex == headindex) self->s.frame = 0; + else if(self->s.frame < FRAME_crdeath1 && self->s.frame != 0) self->s.frame = FRAME_death308; + self->s.modelindex2 = 0; // remove linked weapon model +//ZOID + self->s.modelindex3 = 0; // remove linked ctf flag +//ZOID + + self->client->zc.route_trace = false; + if(self->client->respawn_time <= level.time) + { + if(self->svflags & SVF_MONSTER) + { + self->client->respawn_time = level.time; + CopyToBodyQue (self); + PutBotInServer(self); + } + } + } + else + { + Bots_Move_NORM (self); + if(!self->inuse) return; //removed botself + + client = self->client; + + ClientBeginServerFrame (self); + } + if (self->linkcount != self->monsterinfo.linkcount) + { +// self->monsterinfo.linkcount = self->linkcount; + M_CheckGround (self); + } + M_CatagorizePosition (self); + BotEndServerFrame (self); + self->nextthink = level.time + FRAMETIME; + return; +} + +//---------------------------------------------------------------- +//Initialize Bot +// +// Initialize bot edict +// +//---------------------------------------------------------------- + +void InitializeBot (edict_t *ent,int botindex ) +{ + gclient_t *client; + char pinfo[200]; + int index; + int i; + + index = ent-g_edicts-1; + ent->client = &game.clients[index]; + + client = ent->client; + + memset (&client->zc,0,sizeof(zgcl_t)); + memset (&client->pers, 0, sizeof(client->pers)); + memset (&client->resp, 0, sizeof(client->resp)); + + //set botindex NO. + client->zc.botindex = botindex; + + client->resp.enterframe = level.framenum; + + //set netname model skil and CTF team + sprintf(pinfo,"\\rate\\25000\\msg\\1\\fov\\90\\skin\\%s/%s\\name\\%s\\hand\\0",Bot[botindex].model,Bot[botindex].skin,Bot[botindex].netname); + ent->client->resp.ctf_team = Bot[botindex].team; //CTF_TEAM1,CTF_TEAM2 + + ClientUserinfoChanged (ent, pinfo); + + client->pers.health = 100; + client->pers.max_health = 100; + + client->pers.max_bullets = 200; + client->pers.max_shells = 100; + client->pers.max_rockets = 50; + client->pers.max_grenades = 50; + client->pers.max_cells = 200; + client->pers.max_slugs = 50; + + // RAFAEL + client->pers.max_magslug = 50; + client->pers.max_trap = 5; + + ent->client->pers.connected = false; + gi.dprintf ("%s connected\n", ent->client->pers.netname); +// gi.bprintf (PRINT_HIGH, "%s entered the game\n", ent->client->pers.netname); + + if(ctf->value) gi.bprintf(PRINT_HIGH, "%s joined the %s team.\n", + client->pers.netname, CTFTeamName(ent->client->resp.ctf_team)); + else gi.bprintf (PRINT_HIGH, "%s entered the game\n", + client->pers.netname); +} + +void PutBotInServer (edict_t *ent) +{ + edict_t *touch[MAX_EDICTS]; + int i,j,entcount; + gitem_t *item; + gclient_t *client; + vec3_t spawn_origin, spawn_angles; + trace_t rs_trace; + + + zgcl_t *zc; + + zc = &ent->client->zc; + +//test +// item = FindItem("Trap"); +// ent->client->pers.inventory[ITEM_INDEX(item)] = 100; +//test + + //current weapon + client = ent->client; + item = Fdi_BLASTER;//FindItem("Blaster"); + client->pers.selected_item = ITEM_INDEX(item); + client->pers.inventory[client->pers.selected_item] = 1; + client->pers.weapon = item; + client->silencer_shots = 0; + client->weaponstate = WEAPON_READY; + client->newweapon = NULL; + + //clear powerups + client->quad_framenum = 0; + client->invincible_framenum = 0; + client->breather_framenum = 0; + client->enviro_framenum = 0; + client->grenade_blew_up = false; + client->grenade_time = 0; + + j = zc->botindex; + i = zc->routeindex; + memset (&client->zc,0,sizeof(zgcl_t)); + zc->botindex = j; + zc->routeindex = i; + +//ZOID + client->ctf_grapple = NULL; + + item = FindItem("Grapple"); + if(ctf->value) client->pers.inventory[ITEM_INDEX(item)] = 1; //ponpoko +//ZOID + + // clear entity values + ent->classname = "player"; + ent->movetype = MOVETYPE_STEP; + ent->solid = SOLID_BBOX; + ent->model = "players/male/tris.md2"; + VectorSet (ent->mins, -16, -16, -24); + VectorSet (ent->maxs, 16, 16, 32); + + ent->health = ent->client->pers.health; + ent->max_health = ent->client->pers.max_health; + ent->gib_health = -40; + + ent->mass = 200; + ent->target_ent = NULL; + ent->s.frame = 0; + + // clear entity state values + ent->s.modelindex = 255; // will use the skin specified model + ent->s.skinnum = ent - g_edicts - 1; + ShowGun(ent); // ### Hentai ### special gun model + + ent->s.sound = 0; + + ent->monsterinfo.scale = MODEL_SCALE; + + ent->pain = player_pain; + ent->die = player_die; + ent->touch = NULL; + + ent->moveinfo.decel = level.time; + ent->pain_debounce_time = level.time; + ent->targetname = NULL; + + ent->moveinfo.speed = 1.0; //ジャンプ中の移動率について追加 + ent->moveinfo.state = GETTER; //CTFステータス初期化 + + ent->prethink = NULL; + ent->think = Bot_Think; + ent->nextthink = level.time + FRAMETIME; + ent->svflags /*|*/= SVF_MONSTER ; + ent->s.renderfx = 0; + ent->s.effects = 0; + + SelectSpawnPoint (ent, spawn_origin, spawn_angles); + VectorCopy (spawn_origin, ent->s.origin); + VectorCopy (spawn_angles, ent->s.angles); + spawn_origin[2] -= 300; + rs_trace = gi.trace(ent->s.origin,ent->mins,ent->maxs,spawn_origin,ent,MASK_SOLID); + if(!rs_trace.allsolid) VectorCopy (rs_trace.endpos, ent->s.origin); + VectorSet(ent->velocity,0,0,0); + ent->moveinfo.speed = 0; + ent->groundentity = rs_trace.ent; + ent->client->ps.pmove.pm_flags &= ~PMF_DUCKED; + + Set_BotAnim(ent,ANIM_BASIC,FRAME_run1,FRAME_run6); + client->anim_run = true; + + ent->client->ctf_grapple = NULL; + ent->client->quad_framenum = level.framenum; + ent->client->invincible_framenum = level.framenum; + ent->client->enviro_framenum = level.framenum; + ent->client->breather_framenum = level.framenum; + ent->client->weaponstate = WEAPON_READY; + ent->takedamage = DAMAGE_AIM; + ent->air_finished = level.time + 12; + ent->clipmask = MASK_PLAYERSOLID;//MASK_MONSTERSOLID; + ent->flags &= ~FL_NO_KNOCKBACK; + + ent->client->anim_priority = ANIM_BASIC; +// ent->client->anim_run = true; + ent->s.frame = FRAME_run1-1; + ent->client->anim_end = FRAME_run6; + ent->deadflag = DEAD_NO; + ent->svflags &= ~SVF_DEADMONSTER; + + zc->waitin_obj = NULL; + zc->first_target = NULL; + zc->first_target = NULL; + zc->zcstate = STS_IDLE; + + if(ent->client->resp.enterframe == level.framenum && !chedit->value) + { + gi.WriteByte (svc_muzzleflash); + gi.WriteShort (ent-g_edicts); + gi.WriteByte (MZ_LOGIN); + gi.multicast (ent->s.origin, MULTICAST_PVS); + } + else if(!chedit->value) + { + gi.WriteByte (svc_muzzleflash); + gi.WriteShort (ent-g_edicts); + gi.WriteByte (MZ_RESPAWN); + gi.multicast (ent->s.origin, MULTICAST_PVS); + } + gi.linkentity (ent); + VectorAdd (spawn_origin, ent->mins, ent->absmin); + VectorAdd (spawn_origin, ent->maxs, ent->absmax); + entcount = gi.BoxEdicts ( ent->absmin ,ent->absmax,touch,MAX_EDICTS,AREA_SOLID); + while (entcount-- > 0) + { + if(Q_stricmp (touch[entcount]->classname, "player") == 0) + if(touch[entcount] != ent) + T_Damage (touch[entcount], ent, ent, vec3_origin, touch[entcount]->s.origin, vec3_origin, 100000, 0, DAMAGE_NO_PROTECTION, MOD_TELEFRAG); + } + + if(ctf->value) + { + CTFPlayerResetGrapple(ent); + client->zc.ctfstate = CTFS_OFFENCER; + } + + + gi.linkentity (ent); + G_TouchTriggers (ent); +} + +//---------------------------------------------------------------- +//Spawn Bot +// +// spawn bots +// +// int i index of bot list +// +//---------------------------------------------------------------- + +qboolean SpawnBot(int i) +{ + edict_t *bot,*ent; + int k,j; + + +//gi.cprintf (NULL,PRINT_HIGH,"Called %s %s %s\n",Bot[i].netname,Bot[i].model,Bot[i].skin); +//return false; + + if( Get_NumOfPlayer () >= game.maxclients ) + { + gi.cprintf (NULL,PRINT_HIGH,"Can't add bots\n"); + return false; + } + + bot = Get_NewClient(); + if(bot == NULL) return false; + + InitializeBot( bot , i); + PutBotInServer ( bot ); + + j = targetindex; + if(chedit->value) + { + for(k = CurrentIndex - 1;k > 0 ;k--) + { + if(Route[k].index == 0) break; + + if(Route[k].state == GRS_NORMAL) + { + if(--j <= 0) break; + } + } + + bot->client->zc.rt_locktime = level.time + FRAMETIME * 20; + bot->client->zc.route_trace = true; + bot->client->zc.routeindex = k; + VectorCopy(Route[k].Pt,bot->s.origin); + VectorAdd (bot->s.origin, bot->mins, bot->absmin); + VectorAdd (bot->s.origin, bot->maxs, bot->absmax); + bot->client->ps.pmove.pm_flags |= PMF_DUCKED; + gi.linkentity (bot); +// bot->s.modelindex = 0; + + gi.WriteByte (svc_muzzleflash); + gi.WriteShort (bot-g_edicts); + gi.WriteByte (MZ_LOGIN); + gi.multicast (bot->s.origin, MULTICAST_PVS); + + ent = &g_edicts[1]; + if(ent->inuse && ent->client && !(ent->svflags & SVF_MONSTER)) + { + ent->takedamage = DAMAGE_NO; + ent->movetype = MOVETYPE_NOCLIP; + ent->target_ent = bot; + ent->solid = SOLID_NOT; + ent->client->ps.pmove.pm_type = PM_FREEZE; + ent->client->ps.pmove.pm_flags |= PMF_NO_PREDICTION ; + VectorCopy(ent->s.origin,ent->moveinfo.start_origin); + } + } + + return true; +} + +//---------------------------------------------------------------- +//Spawn Call +// +// spawn bots +// +// int i index of bot list +// +//---------------------------------------------------------------- +void Bot_SpawnCall() +{ + int i; + + for(i = 0;i < MAXBOTS;i++) + { + if(Bot[i].spflg == BOT_SPRESERVED) + { + if(SpawnBot(i)) Bot[i].spflg = BOT_SPAWNED; + else + { + Bot[i].spflg = BOT_SPAWNNOT; + targetindex = 0; + } + SpawnWaitingBots--; + break; + } + } +} +//---------------------------------------------------------------- +//Spawn Bot Reserving +// +// spawn bots reserving +// +//---------------------------------------------------------------- +void SpawnBotReserving() +{ + int i; + + for(i = 0;i < MAXBOTS; i++) + { + if(Bot[i].spflg == BOT_SPAWNNOT) + { + Bot[i].spflg = BOT_SPRESERVED; + SpawnWaitingBots++; + return; + } + } + gi.cprintf (NULL, PRINT_HIGH, "Now max of bots(%i) already spawned.\n",MAXBOTS); +} +//---------------------------------------------------------------- +//Spawn Bot Reserving 2 +// +// randomized spawn bots reserving +// +//---------------------------------------------------------------- +void SpawnBotReserving2(int *red,int *blue) +{ + int i,j; + + j = (int)(random() * ListedBots); + + for(i = 0;i < ListedBots; i++,j++) + { + if(j >= ListedBots) j = 0; + if(Bot[j].spflg == BOT_SPAWNNOT) + { + Bot[j].spflg = BOT_SPRESERVED; + SpawnWaitingBots++; + if(*red > *blue) Bot[j].team = 2; + else Bot[j].team = 1; + + if(Bot[j].team == 1) *red = *red + 1; + else if(Bot[j].team == 2) *blue = *blue + 1; +//gi.cprintf(NULL,PRINT_HIGH,"team %i\n",Bot[j].team); + return; + } + } + SpawnBotReserving(); +} + +//---------------------------------------------------------------- +//Remove Bot +// +// remove bots +// +// int i index of bot list +// +//---------------------------------------------------------------- +void RemoveBot() +{ + int i; + int botindex; + edict_t *e,*ent; + gclient_t *client; + + for(i = MAXBOTS - 1;i >= 0;i--) + { + if(Bot[i].spflg == BOT_SPAWNED || Bot[i].spflg == BOT_NEXTLEVEL) + { + break; + } + } + + if(i < 0) + { + gi.cprintf (NULL, PRINT_HIGH, "No Bots in server."); + return; + } + + botindex = i; + + e = &g_edicts[(int)maxclients->value]; + for ( i = maxclients->value ; i >= 1 ; i--, e--) + { + if(!e->inuse) continue; + client = /*e->client;*/&game.clients[i - 1]; + if(client == NULL) continue; + // the first couple seconds of server time can involve a lot of + // freeing and allocating, so relax the replacement policy + if (!client->pers.connected && (e->svflags & SVF_MONSTER)) + { + if(client->zc.botindex == botindex) + { + if(Bot[botindex].spflg != BOT_NEXTLEVEL) Bot[botindex].spflg = BOT_SPAWNNOT; + else Bot[botindex].spflg = BOT_SPRESERVED; + + gi.bprintf (PRINT_HIGH, "%s disconnected\n", e->client->pers.netname); + + // send effect + gi.WriteByte (svc_muzzleflash); + gi.WriteShort (e-g_edicts); + gi.WriteByte (MZ_LOGOUT); + gi.multicast (e->s.origin, MULTICAST_PVS); + + e->s.modelindex = 0; + e->solid = SOLID_NOT; + + if(ctf->value) CTFPlayerResetGrapple(e); + + gi.linkentity (e); + + e->inuse = false; + G_FreeEdict (e); + + if(targetindex) + { + ent = &g_edicts[1]; + + if(ent->inuse) + { + ent->health = 100; + ent->movetype = MOVETYPE_WALK; + ent->takedamage = DAMAGE_AIM; + ent->target_ent = NULL; + ent->solid = SOLID_BBOX; + ent->client->ps.pmove.pm_type = PM_NORMAL; + ent->client->ps.pmove.pm_flags = PMF_DUCKED; + VectorCopy(ent->moveinfo.start_origin,ent->s.origin); + VectorCopy(ent->moveinfo.start_origin,ent->s.old_origin); + } + targetindex = 0; + } + return; + } + } + } + gi.error ("Can't remove bot."); +} + +//---------------------------------------------------------------- +//Level Change Removing +// +// +// +//---------------------------------------------------------------- +void Bot_LevelChange() +{ + int i,j,k; + + j = 0,k = 0; + + for(i = 0;i < MAXBOTS;i++) + { + if(Bot[i].spflg) + { + if(Bot[i].spflg == BOT_SPAWNED) + { + k++; + Bot[i].spflg = BOT_NEXTLEVEL; + } + j++; + } + } + for(i = 0;i < k; i++) + { + RemoveBot(); + } + + SpawnWaitingBots = k;//j; +} +//---------------------------------------------------------------- +// +// Ragomode menu +// +void ZigockClientJoin(edict_t *ent,int zclass) +{ + PMenu_Close(ent); + + ent->moveinfo.sound_end = CLS_ALPHA; //PutClientの前にクラス決定 + + ent->svflags &= ~SVF_NOCLIENT; + PutClientInServer (ent); + // add a teleportation effect + ent->s.event = EV_PLAYER_TELEPORT; + // hold in place briefly + ent->client->ps.pmove.pm_flags = PMF_TIME_TELEPORT; + ent->client->ps.pmove.pm_time = 14; + + if(ctf->value) + { + gi.bprintf(PRINT_HIGH, "%s joined the %s team.\n", + ent->client->pers.netname, CTFTeamName(ent->client->resp.ctf_team/*desired_team*/)); + } +} +void ClientJoinAsAlpha(edict_t *ent,pmenu_t *entries) +{ + ZigockClientJoin(ent,1); +} + +pmenu_t zgjoinmenu[] = { + { "*Quake II", PMENU_ALIGN_CENTER, NULL, NULL }, + { "*3rd Zigock Rago", PMENU_ALIGN_CENTER, NULL, NULL }, + { NULL, PMENU_ALIGN_CENTER, NULL, NULL }, + { NULL, PMENU_ALIGN_CENTER, NULL, NULL }, + { "alpha", PMENU_ALIGN_LEFT, NULL, ClientJoinAsAlpha }, + { "beta", PMENU_ALIGN_LEFT, NULL, ClientJoinAsAlpha }, + { "gamma", PMENU_ALIGN_LEFT, NULL, ClientJoinAsAlpha }, + { "delta", PMENU_ALIGN_LEFT, NULL, ClientJoinAsAlpha }, + { "epsilon", PMENU_ALIGN_LEFT, NULL, ClientJoinAsAlpha }, + { "zeta", PMENU_ALIGN_LEFT, NULL, ClientJoinAsAlpha }, + { "eta", PMENU_ALIGN_LEFT, NULL, ClientJoinAsAlpha }, + { NULL, PMENU_ALIGN_LEFT, NULL, NULL }, + { "Use [ and ] to move cursor", PMENU_ALIGN_LEFT, NULL, NULL }, + { "ENTER to select class", PMENU_ALIGN_LEFT, NULL, NULL }, + { "ESC to Exit Menu", PMENU_ALIGN_LEFT, NULL, NULL }, + { "(TAB to Return)", PMENU_ALIGN_LEFT, NULL, NULL }, +}; + +void ZigockJoinMenu(edict_t *ent) +{ + PMenu_Open(ent, zgjoinmenu,4, sizeof(zgjoinmenu) / sizeof(pmenu_t)); +} + +qboolean ZigockStartClient(edict_t *ent) +{ + if (ent->moveinfo.sound_end != CLS_NONE) + return false; + + // start as 'observer' + ent->movetype = MOVETYPE_NOCLIP; + ent->solid = SOLID_NOT; + ent->svflags |= SVF_NOCLIENT; + ent->client->ps.gunindex = 0; + gi.linkentity (ent); + + ZigockJoinMenu(ent); + return true; +} + + +//=============================== + +// AirStrike + +//=============================== +static void AirSight_Explode (edict_t *ent) +{ + vec3_t origin; + int mod; + +// if (ent->owner->client && !(ent->owner->svflags & SVF_DEADMONSTER)) +// PlayerNoise(ent->owner, ent->s.origin, PNOISE_IMPACT); + + gi.sound (ent, CHAN_AUTO, gi.soundindex("3zb/airexp.wav"), 1, ATTN_NONE, 0); + + //FIXME: if we are onground then raise our Z just a bit since we are a point? + mod = MOD_AIRSTRIKE; + + T_RadiusDamage(ent, ent->owner, ent->dmg, ent->enemy, ent->dmg_radius, mod); + + VectorMA (ent->s.origin, -0.02, ent->velocity, origin); + gi.WriteByte (svc_temp_entity); + if (ent->waterlevel) + { + gi.WriteByte (TE_ROCKET_EXPLOSION_WATER); + } + else + { + gi.WriteByte (TE_ROCKET_EXPLOSION); + } + gi.WritePosition (origin); + gi.multicast (ent->s.origin, MULTICAST_PHS); + + G_FreeEdict (ent); +} + +void AirSight_Think(edict_t *ent) +{ +// gi.sound (ent, CHAN_AUTO, gi.soundindex("medic/medatck1.wav"), 1, ATTN_NORM, 0); + gi.sound (ent, CHAN_BODY, gi.soundindex("3zb/airloc.wav"), 1, ATTN_NONE, 0); + + ent->dmg = 120 + random() * 60; + ent->dmg_radius = 200; + + ent->s.modelindex = gi.modelindex ("sprites/airsight.sp2"); + VectorCopy(ent->target_ent->s.origin,ent->s.origin); + + if( ent->owner->client->resp.ctf_team == CTF_TEAM2 && ctf->value) + { + ent->s.frame = 1; + } + else ent->s.frame = 0; + + ent->think = AirSight_Explode; + ent->nextthink = level.time + FRAMETIME * 6; + gi.linkentity (ent); +} +void AirStrike_Think(edict_t *ent) +{ + int i,j; + edict_t *target,*sight; + trace_t rs_trace; + + vec3_t point; + + ent->nextthink = level.time + ent->moveinfo.speed * 0.5 /300; + ent->think = G_FreeEdict; +// ent->s.modelindex = gi.modelindex ("models/ships/bigviper/tris.md2"); + + VectorCopy(ent->s.origin,point); + point[2] = ent->moveinfo.start_angles[2]; + + j = 1; + for ( i = 1 ; i <= maxclients->value; i++) + { + target = &g_edicts[i]; + if(!target->inuse || target->deadflag || target == ent->owner) continue; + + if( target->classname[0] == 'p') + { + //ctf ならチームメイト無視 + if(!ctf->value || (ctf->value && ent->owner->client->resp.ctf_team != target->client->resp.ctf_team)) + { + rs_trace = gi.trace (point,NULL,NULL,target->s.origin,ent, CONTENTS_SOLID | CONTENTS_WINDOW | CONTENTS_LAVA | CONTENTS_SLIME); + + if(rs_trace.fraction == 1.0) + { + sight = G_Spawn(); + + sight->classname = "airstrike"; + sight->think = AirSight_Think; + sight->nextthink = level.time + FRAMETIME * 2 * (float)j; + sight->movetype = MOVETYPE_NOCLIP; + sight->solid = SOLID_NOT; + sight->owner = ent->owner; + sight->target_ent = target; + gi.linkentity (sight); + j++; + } + } + } + } + +} +void Cmd_AirStrike(edict_t *ent) +{ + edict_t *viper; + trace_t rs_trace; + + vec3_t strpoint,tts,tte,tmp; + + vec_t f; + + VectorCopy(ent->s.origin,strpoint); + strpoint[2] += 8190; + + rs_trace = gi.trace (ent->s.origin,NULL,NULL,strpoint,ent, MASK_SHOT); + + if(!(rs_trace.surface && (rs_trace.surface->flags & SURF_SKY))) + { + gi.cprintf(ent,PRINT_HIGH,"can't call Viper.\n"); + return; + } +/* if((rs_trace.endpos[2] - ent->s.origin[2]) < 300) + { + gi.cprintf(ent,PRINT_HIGH,"can't call Viper.\n"); + }*/ + + VectorCopy(rs_trace.endpos,strpoint); + strpoint[2] -= 16; //ちょっとだけ下へずらす + + f = ent->s.angles[YAW]*M_PI*2 / 360; + tts[0] = cos(f) * (-8190) ; + tts[1] = sin(f) * (-8190) ; + tts[2] = 0; + + tte[0] = cos(f) *8190 ; + tte[1] = sin(f) *8190 ; + tte[2] = 0; + + viper = G_Spawn(); + VectorClear (viper->mins); + VectorClear (viper->maxs); + viper->movetype = /*MOVETYPE_FLYMISSILE;//MOVETYPE_STEP;*/MOVETYPE_NOCLIP; + viper->solid = SOLID_NOT; + viper->owner = ent; + viper->s.modelindex = gi.modelindex ("models/ships/viper/tris.md2"); + + VectorCopy(ent->s.angles,viper->s.angles); + viper->s.angles[2] = 0; + rs_trace = gi.trace (strpoint,NULL,NULL,tts,ent, MASK_SHOT); + tts[0] = cos(f) * (-600) ; + tts[1] = sin(f) * (-600) ; + VectorAdd(rs_trace.endpos,tts,tmp); + VectorCopy(tmp,viper->s.origin); + + + viper->velocity[0] = cos(f) * 300; + viper->velocity[1] = sin(f) * 300; + viper->velocity[2] = 0; + + rs_trace = gi.trace (strpoint,NULL,NULL,tte,ent, MASK_SHOT); + VectorSubtract(viper->s.origin,rs_trace.endpos,tts); + f = VectorLength(tts); + + gi.sound (viper, CHAN_AUTO, gi.soundindex("world/flyby1.wav"), 1, ATTN_NONE, 0); + + gi.sound (ent, CHAN_AUTO, gi.soundindex("world/incoming.wav"), 1, ATTN_NONE, 0); + + viper->nextthink = level.time + f *0.75 /300; + viper->think = AirStrike_Think; + viper->moveinfo.speed = f; + +// viper->s.sound = gi.soundindex ("weapons/rockfly.wav"); + + // viper->s.effects |= EF_ROTATE | EF_COLOR_SHELL; +// viper->s.renderfx |= RF_SHELL_BLUE | RF_SHELL_GREEN; + VectorCopy(strpoint,viper->moveinfo.start_angles); //strikepoint + +// viper->think = Pod_think; +// viper->nextthink = level.time + FRAMETIME; + viper->classname = "viper"; + viper->s.origin[2] += 16; + gi.linkentity (viper); +} + diff --git a/src/bot_za.c b/src/bot_za.c new file mode 100644 index 0000000..30fd9a1 --- /dev/null +++ b/src/bot_za.c @@ -0,0 +1,5903 @@ +#include "bot.h" +#include "q_shared.h" +#include "m_player.h" + +qboolean pickup_pri; + +int targetindex; //debugtarget +char ClientMessage[MAX_STRING_CHARS]; +botinfo_t Bot[MAXBOTS]; +route_t Route[MAXNODES]; +int CurrentIndex; +int SpawnWaitingBots; +float JumpMax = 0; +qboolean JmpTableChk = false; +float JumpTable[FALLCHK_LOOPMAX]; +int botskill; +int trace_priority; +int skullindex; +int headindex; +int mpindex[MPI_INDEX]; //items in map +int ListedBotCount; +gitem_t *zflag_item; +edict_t *zflag_ent; +int zigflag_spawn; +int ListedBots; +edict_t* ExplIndex[MAX_EXPLINDEX]; +edict_t* LaserIndex[MAX_LASERINDEX]; +/*-------------------------*/ +//pre searched items +gitem_t *Fdi_GRAPPLE; +gitem_t *Fdi_BLASTER; +gitem_t *Fdi_SHOTGUN; +gitem_t *Fdi_SUPERSHOTGUN; +gitem_t *Fdi_MACHINEGUN; +gitem_t *Fdi_CHAINGUN; +gitem_t *Fdi_GRENADES; +gitem_t *Fdi_GRENADELAUNCHER; +gitem_t *Fdi_ROCKETLAUNCHER; +gitem_t *Fdi_HYPERBLASTER; +gitem_t *Fdi_RAILGUN; +gitem_t *Fdi_BFG; +gitem_t *Fdi_PHALANX; +gitem_t *Fdi_BOOMER; +gitem_t *Fdi_TRAP; + +gitem_t *Fdi_SHELLS; +gitem_t *Fdi_BULLETS; +gitem_t *Fdi_CELLS; +gitem_t *Fdi_ROCKETS; +gitem_t *Fdi_SLUGS; +gitem_t *Fdi_MAGSLUGS; +/*--------------------------*/ + +/* +FIRE_PRESTAYFIRE +FIRE_STAYFIRE +FIRE_IGNORE +FIRE_REFUGE +FIRE_EXPAVOID +FIRE_QUADUSE +FIRE_AVOIDINV +FIRE_JUMPROC +*/ +//combat flag set +int FFlg[MAX_BOTSKILL] += +{ +//skill 0 + FIRE_PRESTAYFIRE | FIRE_STAYFIRE | FIRE_REFUGE, +//skill 1 + FIRE_REFUGE | FIRE_PRESTAYFIRE | FIRE_STAYFIRE | FIRE_REFUGE, +//skill 2 + FIRE_REFUGE | FIRE_PRESTAYFIRE | FIRE_IGNORE | FIRE_QUADUSE, +//skill 3 + FIRE_REFUGE | FIRE_EXPAVOID | FIRE_IGNORE | FIRE_QUADUSE, +//skill 4 + FIRE_REFUGE | FIRE_EXPAVOID | FIRE_IGNORE | FIRE_QUADUSE, +//skill 5 + FIRE_EXPAVOID | FIRE_IGNORE | FIRE_QUADUSE | FIRE_AVOIDINV | FIRE_IGNORE | FIRE_JUMPROC, +//skill 6 + FIRE_EXPAVOID | FIRE_IGNORE | FIRE_QUADUSE | FIRE_AVOIDINV | FIRE_IGNORE | FIRE_JUMPROC, +//skill 7 + FIRE_EXPAVOID | FIRE_IGNORE | FIRE_QUADUSE | FIRE_AVOIDINV| FIRE_IGNORE | FIRE_JUMPROC, +//skill 8 + FIRE_EXPAVOID | FIRE_IGNORE | FIRE_QUADUSE | FIRE_AVOIDINV| FIRE_IGNORE | FIRE_JUMPROC, +//skill 9 + FIRE_EXPAVOID | FIRE_IGNORE | FIRE_QUADUSE | FIRE_AVOIDINV| FIRE_IGNORE | FIRE_JUMPROC +}; + +qboolean BotApplyStrength(edict_t *ent) +{ + static gitem_t *tech = NULL; + + if (!tech) + tech = FindItemByClassname("item_tech2"); + if (tech && ent->client + && ent->client->pers.inventory[ITEM_INDEX(tech)]) + return true; + + return false; +} +qboolean BotApplyResistance(edict_t *ent) +{ + static gitem_t *tech = NULL; + + if (!tech) + tech = FindItemByClassname("item_tech1"); + if (tech && ent->client + && ent->client->pers.inventory[ITEM_INDEX(tech)]) + return true; + + return false; +} + +//return foundedenemy +int Bot_SearchEnemy (edict_t *ent) +{ + zgcl_t *zc; //zc's address + qboolean tmpflg; //temporary + edict_t *target,*trent; + trace_t rs_trace; + + int i,j,k; + int foundedenemy; + + float pitch,yaw; + float vr,hr; + + char *entcln; + + vec3_t trmin; + + zc = &ent->client->zc; + + vr = (float)Bot[zc->botindex].param[BOP_VRANGE]; + hr = (float)Bot[zc->botindex].param[BOP_HRANGE]; + + if(vr > 180) vr = 180; + if(hr > 180) hr = 180; + if(vr < 10) vr = 10; + if(hr < 10) hr = 10; + //------------------------------------------- + //search for enemy + foundedenemy = 0; + target = NULL; + + tmpflg = false; //viewable flag + if(zc->first_target != NULL){ + if( Bot_trace(ent,zc->first_target)){ + tmpflg = true; + foundedenemy++; + } + } + +// if(zc->ctfstate == CTFS_SUPPORTER) zc->ctfstate = CTFS_OFFENCER; + + // blue or red? + if(ctf->value) + { + if(ent->client->resp.ctf_team == CTF_TEAM1) k = ITEM_INDEX(FindItem("Blue Flag")); + else k = ITEM_INDEX(FindItem("Red Flag")); + } + else k = ITEM_INDEX(FindItem("ZB Flag")); + + // decide da sorting first or last + if(random() < 0.5) j = 0; + else j = -1; + + if(ent->client->pers.inventory[ITEM_INDEX(zflag_item)]) + { + ent->client->zc.tmplstate = TMS_LEADER; + ent->client->zc.followmate = NULL; + } + + for ( i = 1 ; i <= maxclients->value && target == NULL ; i++) + { + if(j){ + entcln = g_edicts[i].classname; + trent = &g_edicts[i]; + } + else{ + entcln = g_edicts[(int)(maxclients->value) - i +1].classname; + trent = &g_edicts[(int)(maxclients->value) - i +1]; + } + + if(!trent->inuse || ent == trent || zc->first_target == trent ||trent->deadflag) continue; + + if( entcln[0] == 'p' && trent->movetype != MOVETYPE_NOCLIP ){ + if(Bot_traceS(ent,trent)) + { + VectorSubtract (trent->s.origin, ent->s.origin, trmin); + //not ctf mode and sameteam + if(!ctf->value && OnSameTeam(ent,trent)) + { + if(trent->client->zc.first_target) + { + if(Bot_traceS(ent,trent->client->zc.first_target)) target = trent->client->zc.first_target; + } + if(trmin[2] < JumpMax && VectorLength(trmin) < 400) + { + yaw = (float)Bot[ent->client->zc.botindex].param[BOP_TEAMWORK]; + if(trent->client->pers.inventory[ITEM_INDEX(zflag_item)]) + { + trent->client->zc.tmplstate = TMS_LEADER; + trent->client->zc.followmate = NULL; + if((9 * random()) < yaw) + { + ent->client->zc.tmplstate = TMS_FOLLOWER; + ent->client->zc.followmate = trent; + } + } + else + { + if((9 * random()) < yaw) + { +//gi.bprintf(PRINT_HIGH,"Team stateON\n"); + //相手がリーダー + if(trent->client->zc.tmplstate == TMS_LEADER) + { + trent->client->zc.followmate = NULL; + if(ent->client->zc.tmplstate == TMS_LEADER) + { + if(random() < 0.5) + { + ent->client->zc.tmplstate = TMS_FOLLOWER; + ent->client->zc.followmate = trent; + } + else + { + ent->client->zc.tmplstate = TMS_LEADER; + ent->client->zc.followmate = NULL; + trent->client->zc.tmplstate = TMS_FOLLOWER; + trent->client->zc.followmate = ent; + } + } + else + { + ent->client->zc.tmplstate = TMS_FOLLOWER; + ent->client->zc.followmate = trent; + } + } + else + { + if(ent->client->zc.tmplstate == TMS_LEADER + || random() < 0.5) + { + ent->client->zc.tmplstate = TMS_LEADER; + ent->client->zc.followmate = NULL; + trent->client->zc.tmplstate = TMS_FOLLOWER; + trent->client->zc.followmate = ent; + } + else + { + ent->client->zc.tmplstate = TMS_FOLLOWER; + ent->client->zc.followmate = trent; + trent->client->zc.tmplstate = TMS_LEADER; + trent->client->zc.followmate = NULL; + } + } + } + } + } + } + else if(ctf->value && ent->client->resp.ctf_team == trent->client->resp.ctf_team) + { + //have flag + if(trent->client->pers.inventory[k]) + { + if(ent->client->zc.ctfstate == CTFS_OFFENCER) + { + ent->client->zc.ctfstate = CTFS_SUPPORTER; + ent->client->zc.followmate = trent; + } + // if carrier have enemy + if(trent->client->zc.first_target != NULL) + { + if(trent->client->zc.first_target->classname[0] == 'p') + { + target = trent->client->zc.first_target; + } + } + // if carrier tracing route + if(trent->client->zc.route_trace && (trent->client->zc.routeindex - 2) > CurrentIndex) + { + zc->routeindex = trent->client->zc.routeindex - 2; + } +// ent->s.angles[YAW] = Get_yaw (trmin); +// if(ent->client->zc.ctfstate == CTFS_SUPPORTER ){ +// zc->first_target = NULL; +// break; +// } + } + } + else + { + foundedenemy++; + if(!tmpflg && target == NULL) + { + pitch = Get_pitch(trmin); + pitch = fabs(pitch - ent->s.angles[PITCH]); + if(pitch > 180) pitch = 360 - pitch; + //if(ent->client->zc.zcstate & STS_WAITS) target = trent; + if(pitch <= vr) + { + yaw = Get_yaw(trmin); + yaw = fabs(yaw - ent->s.angles[YAW]); + if(yaw > 180) yaw = 360 - yaw; + if(yaw <= hr || (ent->client->zc.zcstate & STS_WAITS)) target = trent; + } + } + // + if(!tmpflg && target == NULL && trent->mynoise && trent->mynoise2) + { + if(Bot[ent->client->zc.botindex].param[BOP_NOISECHK]) + { + if(trent->mynoise->teleport_time >= (level.time - FRAMETIME)) + { + VectorSubtract (trent->mynoise->s.origin, ent->s.origin, trmin); + if(VectorLength(trmin) < 300) + { + pitch = (float)Bot[ent->client->zc.botindex].param[BOP_REACTION]; + if((9 * random()) < pitch) target = trent; + } + } + if(target == NULL && trent->mynoise2->teleport_time >= (level.time - FRAMETIME)) + { + VectorSubtract (trent->mynoise->s.origin, ent->s.origin, trmin); + if(VectorLength(trmin) < 100) + { + pitch = (float)Bot[ent->client->zc.botindex].param[BOP_REACTION]; + if((9 * random()) < pitch) target = trent; + } + } + } + } + } + } + //音のみで場所を判断 + else + { + if(Bot[ent->client->zc.botindex].param[BOP_NOISECHK] + && Bot[ent->client->zc.botindex].param[BOP_ESTIMATE] + && !tmpflg && trent->mynoise) + { + + if(trent->mynoise->teleport_time >= (level.time - FRAMETIME)) + { + AngleVectors (trent->client->v_angle, trmin, NULL, NULL); + VectorScale(trmin,200,trmin); + VectorAdd(trent->s.origin,trmin,trmin); + rs_trace = gi.trace(trent->s.origin,NULL,NULL,trmin,trent,MASK_SHOT); + + VectorSubtract (ent->s.origin, rs_trace.endpos, trmin); + if(VectorLength(trmin) < 500) + { + VectorCopy(rs_trace.endpos,trmin); + rs_trace = gi.trace(ent->s.origin,NULL,NULL,trmin,ent,MASK_SHOT); + pitch = (float)Bot[ent->client->zc.botindex].param[BOP_REACTION]; + + if(rs_trace.fraction == 1.0 && (9 * random()) < pitch) + { + target = trent; + ent->client->zc.battlemode |= FIRE_ESTIMATE; + VectorCopy(trmin,ent->client->zc.vtemp); + } + } + } + } + } + } + } + if(target && !tmpflg) zc->first_target = target; + else if(target && zc->first_target) + { + if(Get_KindWeapon(target->client->pers.weapon) > + Get_KindWeapon(zc->first_target->client->pers.weapon)) zc->first_target = target; + } +// ent->client->zc.zcstate &= ~CTS_COMBS; //clear combat state + + return (foundedenemy); +} + +void Bot_SearchItems (edict_t *ent) +{ + zgcl_t *zc; //zc's address + qboolean wstayf,q; //weaponflag + edict_t *target,*trent; + + vec3_t touchmin,touchmax; + vec3_t trmin,trmax; + + gitem_t *item; + char *entcln; + + float x,yaw; //temporary + + int i,j,k,conts; + + trace_t rs_trace; + + zc = &ent->client->zc; + + + if((zc->tmpcount++) & 1) return; + + j = 0; + q = false;//rocket jump needed + //search Items + if(ctf->value) + { + if(ent->moveinfo.state == SUPPORTER) j = -1; + else if( ent->target_ent != NULL) + { + if( ent->moveinfo.state == CARRIER && ent->target_ent->classname[6] == 'F') j = -1; + } + else if( ent->client->ctf_grapple != NULL) j = -1; + } + trent = zc->second_target; + if( trent != NULL && !j ) + { + x = random(); + if( trent->classname[0] == 'w') j = -1; + else if( trent->classname[0]=='i'){ + if( trent->classname[5]=='q' + || trent->classname[5]=='f' + || trent->classname[5]=='t' + || trent->classname[5]=='i') + j = -1; + } + else if((trent->classname[0] == 'p' && x > 0.2) + || ent->client->ctf_grapple != NULL ) + j= -1; + } + + if( j == 0 ) + { + if ((int)(dmflags->value) & DF_WEAPONS_STAY) wstayf = true; + else wstayf = false; + + target = NULL; + VectorCopy (ent->absmin, touchmin); + VectorCopy (ent->absmax, touchmax); + + // when ctf + if(0/*ctf->value*/){ + touchmin[0] -= 500; + touchmin[1] -= 500; + touchmin[2] -= 500; + touchmax[0] += 500; + touchmax[1] += 500; + touchmax[2] += 598; + } + // when not + else + { + touchmin[0] -= 300; + touchmin[1] -= 300; + touchmin[2] -= 300; + if(ent->waterlevel) touchmin[2] -= 200; + touchmax[0] += 300; + touchmax[1] += 300; + touchmax[2] += 54;// +290); + + if(ent->health > 70 && ent->client->pers.inventory[ITEM_INDEX(Fdi_ROCKETLAUNCHER/*FindItem("Rocket Launcher")*/)]) + { + i = ITEM_INDEX(Fdi_ROCKETS/*FindItem("Rockets")*/); + + if(ent->client->pers.inventory[i] > 0) + { + q = true; + touchmax[2] += 290; + } + } + } + + + if((level.framenum - ent->client->resp.enterframe ) % 64 > 32) j = 0; + else j = -1; + + k = globals.num_edicts + maxclients->value+1; + + for ( i=maxclients->value+1 ; iclassname; + } + else //reverse + { + trent = &g_edicts[k - i]; + entcln = trent->classname; + } + + if(!trent->inuse) continue; + if(trent->solid != SOLID_TRIGGER) + { + if( ent->client->weaponstate == WEAPON_READY && !ent->client->zc.route_trace) + { + if( entcln ) + { + if(entcln[0] == 'f' /*&& trent->classname[5] == 'b'*/ + && trent->health //&&trent->moveinfo.state == PSTATE_BOTTOM + && trent->takedamage)//trent->moveinfo.wait == 0) + { + if(trent->classname[5] == 'b' + && trent->monsterinfo.attack_finished > level.time) continue; + + trmax[0] = (trent->absmin[0] + trent->absmax[0]) / 2; + trmax[1] = (trent->absmin[1] + trent->absmax[1]) / 2; + trmax[2] = (trent->absmin[2] + trent->absmax[2]) / 2; + rs_trace = gi.trace (ent->s.origin, NULL, NULL, trmax ,ent, MASK_SHOT); + + if(rs_trace.ent == trent) + { + // gi.bprintf(PRINT_HIGH,"kkkkkkk\n"); + // continue; + VectorSubtract (trmax, ent->s.origin,trmin); + item = Fdi_BLASTER;//FindItem("Blaster"); +// ent->client->ammo_index = 0; + item->use(ent,item); + //ent->client->ammo_index = 0; + //ent->client->pers.weapon = FindItem("Blaster"); + //ShowGun(ent); + ent->s.angles[YAW] = Get_yaw(trmin); + ent->s.angles[PITCH] = Get_pitch(trmin); + ent->client->buttons |= BUTTON_ATTACK; + ent->client->zc.objshot = true; + } + } + } + } + continue; + } + + + + + if(trent->absmax[0] < touchmin[0]) continue; + if(touchmax[0] < trent->absmin[0]) continue; + if(trent->absmax[1] < touchmin[1]) continue; + if(touchmax[1] < trent->absmin[1]) continue; + if(trent->absmax[2] < touchmin[2]) continue; + if(touchmax[2] < trent->absmin[2]) continue; + + VectorSubtract (trent->s.origin, ent->s.origin, trmin); + yaw = VectorLength(trmin); + +/* if( trent->classname[0] == 'w' || trent->classname[0] == 'R') x = 0; + else if(trent->classname[0]=='i') + { + if(trent->classname[5]=='q' + || trent->classname[5]=='f' + || trent->classname[5]=='t' + || trent->classname[5]=='i') x = 0; + }*/ + /*else*/ if( !ctf->value ) + { +// if(yaw >300 /*|| (trent->s.origin[2] - ent->s.origin[2] ) < -64*/) continue; + } + + if( Bot_trace(ent,trent) ) + { + if(entcln[0] == 'i') + { + if(entcln[5] == 'h') + { + if(Q_stricmp (entcln, "item_health") == 0 ) + { + if( ent->health < 100 && (!pickup_pri || yaw < 96)) target = trent; + } + else if(entcln[12] == 's'){ //item_health_small + if(fabs(trent->s.origin[2] - ent->s.origin[2] + 8) > JumpMax) continue; + target = trent; + } + else if(entcln[12] == 'l') //item_health_large + { + if( ent->health < 100 && (!pickup_pri || yaw < 96)) target = trent; + } + else if(entcln[12] == 'm') //item_health_mega + target = trent; + } + else if(entcln[5] == 'a' && !pickup_pri) + { + if(entcln[11] == 's'){ //item_armor_shard + if(fabs(trent->s.origin[2] - ent->s.origin[2] + 8) > JumpMax) continue; + target = trent; + } + else if(entcln[11] == 'j') //item_armor_jacket + { + if( ent->client->pers.inventory[ITEM_INDEX(FindItem("Jacket Armor"))] < 50 ) target = trent; + } + else if(entcln[11] == 'c') //item_armor_combat + { + if( ent->client->pers.inventory[ITEM_INDEX(FindItem("Combat Armor"))] < 100) target = trent; + } + else if(entcln[11] == 'b') //item_armor_body + { + if( ent->client->pers.inventory[ITEM_INDEX(FindItem("Body Armor"))] < 200) target = trent; + } + else if(entcln[6] == 'd') //item_adrenaline + target = trent; + else if(entcln[6] == 'n') //item_ancient_head + target = trent; + } + else if(entcln[5] == 'f') + { + if( trent->spawnflags & DROPPED_ITEM ) target = trent; + else if(entcln[10] == 't' && entcln[14] == '1') + { + if( ent->client->resp.ctf_team == CTF_TEAM2) target = trent; + else if( ent->client->pers.inventory[ITEM_INDEX(FindItem("Blue Flag"))]) target = trent; + } + else if(entcln[10] == 't' && entcln[14] == '2') + { + if( ent->client->resp.ctf_team == CTF_TEAM1) target = trent; + else if(ent->client->pers.inventory[ITEM_INDEX(FindItem("Red Flag"))]) target = trent; + } + } + else if(entcln[5] == 't') + { + if(!BotApplyResistance(ent)) + if( !BotApplyStrength(ent)) + if( !CTFApplyHaste(ent)) + if( !CTFHasRegeneration(ent)) target = trent; + } + else + { + if(entcln[5] == 'q') //item_quad + target = trent; + else if(entcln[5] == 'p' && entcln[6] == 'a') //item_pack + target = trent; + else if(entcln[5] == 'b' && entcln[6] == 'a') //item_bandolier + target = trent; + else if(entcln[5] == 'e') //item_enviro + target = trent; + else if(entcln[5] == 'b') //item_breather + target = trent; + else if(entcln[5] == 's') //item_silencer + target = trent; + else if(entcln[5] == 'i') //item_invulnerability + target = trent; + else if(entcln[5] == 'p' && entcln[12] == 'h') //Q_stricmp (entcln, "item_power_shield") == 0) + target = trent; + else if(entcln[5] == 'p' && entcln[12] == 'c') //Q_stricmp (entcln, "item_power_screen") == 0) + target = trent; + } + } + else if(entcln[0] == 'a' && !pickup_pri) + { + if(entcln[5] == 'b') //ammo_bullets + { + if( ent->client->pers.inventory[ITEM_INDEX(Fdi_BULLETS/*FindItem("Bullets")*/)] + < ent->client->pers.max_bullets) target = trent; + } + else if(entcln[5] == 's' && entcln[6] == 'h') //ammo_shells + { + if(ent->client->pers.inventory[ITEM_INDEX(Fdi_SHELLS/*FindItem("Shells")*/)] + < ent->client->pers.max_shells) target = trent; + } + else if(entcln[5] == 'g') //ammo_grenades + { + if(ent->client->pers.inventory[ITEM_INDEX(Fdi_GRENADES/*FindItem("Grenades")*/)] + < ent->client->pers.max_grenades) target = trent; + } + else if(entcln[5] == 'r') //ammo_rockets + { + if(ent->client->pers.inventory[ITEM_INDEX(Fdi_ROCKETS/*FindItem("Rockets")*/)] + < ent->client->pers.max_rockets) target = trent; + } + else if(entcln[5] == 's' ) //ammo_slugs + { + if(ent->client->pers.inventory[ITEM_INDEX(Fdi_SLUGS/*FindItem("Slugs")*/)] + < ent->client->pers.max_slugs) target = trent; + } + else if(entcln[5] == 'c' ) //ammo_cells + { + if(ent->client->pers.inventory[ITEM_INDEX(Fdi_CELLS/*FindItem("Cells")*/)] + < ent->client->pers.max_cells) target = trent; + } + else if(entcln[5] == 'm' ) //ammo_magslug + { + if(ent->client->pers.inventory[ITEM_INDEX(Fdi_MAGSLUGS/*FindItem("Mag Slug")*/)] + < ent->client->pers.max_magslug) target = trent; + } + else if(entcln[5] == 't' ) //ammo_trap + { + if(ent->client->pers.inventory[ITEM_INDEX(Fdi_TRAP/*FindItem("Trap")*/)] + < ent->client->pers.max_trap) target = trent; + } + } + else if(entcln[0] == 'w') + { + if(entcln[7] == 's' && entcln[8] == 'h' ) //weapon_shotgun + { + if(!wstayf || (wstayf && !pickup_pri + && (!ent->client->pers.inventory[ITEM_INDEX(Fdi_SHOTGUN/*FindItem("Shotgun")*/)] + || trent->spawnflags & (DROPPED_ITEM | DROPPED_PLAYER_ITEM) ) )) target = trent; + } + else if(entcln[7] == 's') //weapon_supershotgun + { + if(!wstayf || (wstayf + && (!ent->client->pers.inventory[ITEM_INDEX(Fdi_SUPERSHOTGUN/*FindItem("Super Shotgun")*/)] + || trent->spawnflags & (DROPPED_ITEM | DROPPED_PLAYER_ITEM))) ) target = trent; + } + else if(entcln[7] == 'm') //weapon_machinegun + { + if(!wstayf || (wstayf + && (!ent->client->pers.inventory[ITEM_INDEX(Fdi_MACHINEGUN/*FindItem("Machinegun")*/)] + || trent->spawnflags & (DROPPED_ITEM | DROPPED_PLAYER_ITEM)))) target = trent; + } + else if(entcln[7] == 'c') //weapon_chaingun + { + if(!wstayf || (wstayf + && (!ent->client->pers.inventory[ITEM_INDEX(Fdi_CHAINGUN/*FindItem("Chaingun")*/)] + || trent->spawnflags & (DROPPED_ITEM | DROPPED_PLAYER_ITEM))) ) target = trent; + } + else if(entcln[7] == 'g') //weapon_grenadelauncher + { + if(!wstayf || (wstayf + && (!ent->client->pers.inventory[ITEM_INDEX(Fdi_GRENADELAUNCHER/*FindItem("Grenade Launcher")*/)] + || trent->spawnflags & (DROPPED_ITEM | DROPPED_PLAYER_ITEM)))) target = trent; + } + else if(entcln[7]=='r' && entcln[8] == 'o') //weapon_rocketlauncher + { + if(!wstayf || (wstayf + && (!ent->client->pers.inventory[ITEM_INDEX(Fdi_ROCKETLAUNCHER/*FindItem("Rocket Launcher")*/)] + || trent->spawnflags & (DROPPED_ITEM | DROPPED_PLAYER_ITEM)))) target = trent; + } + else if(entcln[7] == 'h') //weapon_hyperblaster + { + if(!wstayf || (wstayf + && (!ent->client->pers.inventory[ITEM_INDEX(Fdi_HYPERBLASTER/*FindItem("HyperBlaster")*/)] + || trent->spawnflags & (DROPPED_ITEM | DROPPED_PLAYER_ITEM)))) target = trent; + } + else if(entcln[7] == 'r') //weapon_railgun + { + if(!wstayf || (wstayf + && (!ent->client->pers.inventory[ITEM_INDEX(Fdi_RAILGUN/*FindItem("Railgun")*/)] + || trent->spawnflags & (DROPPED_ITEM | DROPPED_PLAYER_ITEM)))) target = trent; + } + else if(entcln[7] == 'b' && entcln[8] == 'o')//weapon_boomer + { + if(!wstayf || (wstayf + && (!ent->client->pers.inventory[ITEM_INDEX(Fdi_BOOMER/*FindItem("Ionripper")*/)] + || trent->spawnflags & (DROPPED_ITEM | DROPPED_PLAYER_ITEM)))) target = trent; + } + else if(entcln[7] == 'p') //weapon_phalanx + { + if(!wstayf || (wstayf + && (!ent->client->pers.inventory[ITEM_INDEX(Fdi_PHALANX/*FindItem("Phalanx")*/)] + || trent->spawnflags & (DROPPED_ITEM | DROPPED_PLAYER_ITEM)))) target = trent; + } + else if(entcln[7] == 'b') //weapon_bfg + { + if(!wstayf || (wstayf + && (!ent->client->pers.inventory[ITEM_INDEX(Fdi_BFG/*FindItem("BFG10K")*/)] + || trent->spawnflags & (DROPPED_ITEM | DROPPED_PLAYER_ITEM)))) target = trent; + } + } + else if(entcln[0] == 'R' && !ent->client->zc.route_trace) + { + if(entcln[2] == 'n') + { + if(ctf->value) + { + if(ent->moveinfo.state == CARRIER) + { + if(random() < 0.05) target = trent; + } + else if(ent->moveinfo.state == DEFENDER) + { + if(random() < 0.01) target = trent; + } + else if(random() < 0.02) target = trent; + } + else if(random() < 0.4) target = trent; + } + else if(ent->client->zc.route_trace) + { + if(ent->client->zc.routeindex < CurrentIndex) + { + if(entcln[6] == 'X' && Route[ent->client->zc.routeindex].state == GRS_ONTRAIN) target = trent; + else if(entcln[6] == '2' && Route[ent->client->zc.routeindex].state == GRS_PUSHBUTTON + && Route[ent->client->zc.routeindex].ent == trent) target = trent; + else target = NULL; + } + } + else if(entcln[6] == '3') target = NULL; + } + } + // founded! +/* if( target != NULL && !ctf->value) + { + if(!ent->waterlevel) + { + VectorSubtract(target->s.origin,ent->s.origin,touchmax); + x = touchmax[2]; + touchmax[2] = 0; + iyaw = VectorLength(touchmax); + if( x < -39 ) + { + yaw = iyaw/(-x);*/ +// if( /*yaw < 0.5 &&*/ yaw > 2.5 /*&& iyaw > 64*/) target = NULL; +/* if(target != NULL) + { + if((target->spawnflags & (DROPPED_ITEM | DROPPED_PLAYER_ITEM)) && x < -64) target = NULL; + } + } + if(target != NULL && !ent->waterlevel && x < -60) + { + if( target->classname[0] == 'w' + || (target->classname[0]=='i' && target->classname[5]=='q') + || (target->classname[0]=='i' && target->classname[5]=='f') + || (target->classname[0]=='i' && target->classname[5]=='t') + || (target->classname[0]=='i' && target->classname[5]=='i')) + { + if(iyaw < 64 ) target = NULL; + } + else target = NULL; + } + } + } +*/ + if(target != NULL) + { + conts = gi.pointcontents (target->s.origin); + if(conts & CONTENTS_LAVA) target = NULL; + else if((conts & CONTENTS_SLIME) && ent->client->enviro_framenum <= level.framenum) target = NULL; + } + if(target != NULL && !ctf->value ) + { + if((target->s.origin[2] - ent->s.origin[2]) > 32 && !q) + { + x = target->moveinfo.start_origin[2] - ent->s.origin[2]; + if(x > 54 || x < -24) target = NULL; + else + { + x = target->s.origin[2] - target->moveinfo.start_origin[2]; + if(x > 54 || x < 0) target = NULL; + } + } + else if((target->s.origin[2] - ent->s.origin[2]) <= 100 && q) + { + target = NULL; + } + } + + if(target != NULL ) + { + if( zc->second_target == NULL ) + { + zc->second_target = target; + break; + } + else if(zc->second_target->classname[6] == 'F' && + zc->second_target->classname[6] == 'F') + { + target = NULL; + continue; + } + else + { + if( (target->classname[0] == 'R' && target->classname[0] != 'F') + || target->classname[0] == 'w' + || (target->classname[0]=='i' && target->classname[5]=='q') + || (target->classname[0]=='i' && target->classname[5]=='t') + || (target->classname[0]=='i' && target->classname[5]=='f') + || (target->classname[0]=='i' && target->classname[5]=='i')) + { + VectorSubtract (ent->s.origin, target->s.origin, trmin); + if(ctf->value + && zc->second_target->classname[6] == 'F' + && ent->moveinfo.state != CARRIER) + { + zc->second_target = target; + break; + } + else if( VectorLength(trmin) > 24 ) + { + zc->second_target = target; + break; + } + } + } + } + target = NULL; + } + } +} + +//----------------------------------------------------------------------------------------- +//バクハツ物回避 +//Avoid explotion +// +#define EXPLO_BOXSIZE 64 +qboolean Bot_ExploAvoid(edict_t *ent,vec3_t v) +{ + int i; + + vec3_t absmax,absmin; + vec3_t msmax,msmin; + + if(ent->groundentity == NULL && !ent->waterlevel) return true; + if(!(FFlg[Bot[ent->client->zc.botindex].param[BOP_COMBATSKILL]] & FIRE_EXPAVOID)) return true; + + VectorCopy(v,msmax); + VectorCopy(v,msmin); + msmax[0] += ent->maxs[0]; msmax[1] += ent->maxs[1]; msmax[2] += ent->maxs[2]; + msmin[0] += ent->mins[0]; msmin[1] += ent->mins[1]; msmin[2] += ent->mins[2]; + + for(i = 0;i < MAX_EXPLINDEX;i++) + { + if(ExplIndex[i] != NULL) {if(ExplIndex[i]->inuse == false) ExplIndex[i] = NULL;} + if(ExplIndex[i] != NULL) + { + VectorCopy(ExplIndex[i]->s.origin,absmax); + VectorCopy(absmax,absmin); + absmax[0] += EXPLO_BOXSIZE; absmax[1] += EXPLO_BOXSIZE; absmax[2] += EXPLO_BOXSIZE; + absmin[0] -= EXPLO_BOXSIZE; absmin[1] -= EXPLO_BOXSIZE; absmin[2] -= EXPLO_BOXSIZE; + + if(absmax[0] < msmin[0]) continue; + if(msmax[0] < absmin[0]) continue; + if(absmax[1] < msmin[1]) continue; + if(msmax[1] < absmin[1]) continue; + if(absmax[2] < msmin[2]) continue; + if(msmax[2] < absmin[2]) continue; + + return false; + } + } + return true; +} + +//レーザーのチェック +qboolean CheckLaser(vec3_t pos,vec3_t maxs,vec3_t mins) +{ + int i; + vec3_t v,end,absmax,absmin; + float L1,L2,L3; + + VectorAdd(pos,maxs,absmax); + VectorAdd(pos,mins,absmin); + + for(i = 0;i < MAX_LASERINDEX;i++) + { + if(LaserIndex[i] == NULL) return false; + if (!(LaserIndex[i]->spawnflags & 1)) continue; + + VectorSubtract(pos,LaserIndex[i]->s.origin,v); + L1 = VectorLength(v); + VectorSubtract(pos,LaserIndex[i]->moveinfo.end_origin,v); + L2 = VectorLength(v); + + VectorSubtract(LaserIndex[i]->s.origin,LaserIndex[i]->moveinfo.end_origin,v); + L3 = VectorLength(v); + +// VectorCopy (LaserIndex[i]->s.origin, start); + VectorMA (LaserIndex[i]->s.origin, L3 * L1 / (L1 + L2), LaserIndex[i]->movedir, end); + + VectorSubtract(pos,end,v); + L3 = VectorLength(v); + + if(L3 > L1 || L3 > L2) continue; + +//gi.bprintf(PRINT_HIGH,"Length %f!\n",L3); + + if(end[0] < absmin[0]) continue; + if(absmax[0] < end[0]) continue; + if(end[1] < absmin[1]) continue; + if(absmax[1] < end[1]) continue; + if(end[2] < absmin[2]) continue; + if(absmax[2] < end[2]) continue; +//gi.bprintf(PRINT_HIGH,"Laser Checked! %f\n",L3); + return true; + } + return false; +} + +//----------------------------------------------------------------------------------------- +// BOT移動可能判定new +// bot move test +// return false can't +// true stand +// 2 duck +int Bot_moveT ( edict_t *ent,float ryaw,vec3_t pos,float dist,float *bottom) +{ + float i,yaw; + vec3_t trstart,trend; + vec3_t trmin,trmax,v,vv; + trace_t rs_trace; + float tracelimit; + qboolean moveok; + int contents; + + int tcontents; + + + + tcontents =/* MASK_BOTSOLID*/MASK_BOTSOLIDX;//MASK_PLAYERSOLID /*| CONTENTS_TRANSLUCENT*/; //レーザーには触らない +// if(!ent->waterlevel) tcontents |= CONTENTS_WATER; + + if(/*ent->client->zc.waterstate == WAS_FLOAT*/ent->waterlevel >= 1/*2*/) tracelimit = 75;//75;//61; + else if(ent->client->ps.pmove.pm_flags & PMF_DUCKED) tracelimit = 26; + else tracelimit = JumpMax + 5;//61; + + VectorSet (trmin,-16,-16,-24); + VectorSet (trmax,16,16,3); + + if(ent->client->zc.route_trace) VectorSet (vv,16,16,0); + else VectorSet (vv,16,16,3); + + if(0/*!(ent->client->ps.pmove.pm_flags & PMF_DUCKED) + && (ent->client->zc.n_duckedtime < FRAMETIME * 10 /*&& !ent->client->zc.route_trace)*/) trmax[2] = 31; +// else if(ent->waterlevel && !ent->groundentity) trmax[2] = 32; + else if(ent->client->zc.route_trace + && !(ent->client->ps.pmove.pm_flags & PMF_DUCKED) + && ent->waterlevel < 2) + { + Get_RouteOrigin(ent->client->zc.routeindex,v); + if((v[2] - ent->s.origin[2]) > 20) trmax[2] = 31; + } + + //移動先がどうなっているのか調べる + yaw = ryaw*M_PI*2 / 360; + trend[0] = cos(yaw) * dist ; //start + trend[1] = sin(yaw) * dist ; + trend[2] = 0; + VectorAdd (trend, ent->s.origin, trstart); + + VectorCopy(trstart,trend); + trend[2] += 1; + rs_trace = gi.trace (trstart, trmin, trmax, trend,ent, tcontents); + + trmax[2] += 1; + if(rs_trace.allsolid || rs_trace.startsolid || rs_trace.fraction != 1.0) //前には進めない場合 + { + moveok = false; + VectorCopy (trstart, trend); + + for( i = 4 ; i < (tracelimit + 4) ; i += 4 ) + { + trstart[2] = ent->s.origin[2] + i; + rs_trace = gi.trace (trstart, trmin, vv/*trmax*/, trend,ent, tcontents ); +// rs_trace = gi.trace (trstart, trmin, trmax, trstart,ent, tcontents ); + if(!rs_trace.allsolid && !rs_trace.startsolid && rs_trace.fraction > 0) + { + moveok = true; + break; + } + } + if(!moveok/*i >= tracelimit+4*/) +// if(i >= tracelimit - 4) + { +//gi.bprintf(PRINT_HIGH,"apooX %f >= %f\n",i ,tracelimit); +//if(ent->client->ps.pmove.pm_flags & PMF_DUCKED) gi.bprintf(PRINT_HIGH,"apoo1 %f %f \n",i,trmax[2]); + return false; + } + +// rs_trace = gi.trace (trstart, trmin, trmax, trend,ent, tcontents ); + *bottom = rs_trace.endpos[2] - ent->s.origin[2]; + + if(!ent->client->zc.route_trace) + { +//gi.bprintf(PRINT_HIGH,"apoo2\n"); +//if(ent->client->zc.waterstate == 1 && ryaw == ent->client->zc.moveyaw) gi.bprintf(PRINT_HIGH,"apoo2\n"); + if(rs_trace.plane.normal[2] < 0.7 && (!ent->client->zc.waterstate && ent->groundentity)) return false; + } + else + { + Get_RouteOrigin(ent->client->zc.routeindex,v); + if(rs_trace.plane.normal[2] < 0.7 && v[2] < ent->s.origin[2]) return false; + } + + if( *bottom >/*=*/ tracelimit - 5) + { +//gi.bprintf(PRINT_HIGH,"apooY %f > %f\n",*bottom ,tracelimit - 5); +//gi.bprintf(PRINT_HIGH,"apoo3\n"); +//if(ent->client->zc.waterstate == 1 && ryaw == ent->client->zc.moveyaw) gi.bprintf(PRINT_HIGH,"apoo3\n"); + return false; + } + pos[0] = rs_trace.endpos[0]; + pos[1] = rs_trace.endpos[1]; + pos[2] = rs_trace.endpos[2]; + + if(trmax[2] == 32) + { + if(Bot_ExploAvoid(ent,pos)) + { + if(!CheckLaser(pos,trmax,trmin)) + return true; + } + return false; + } + +// trmax[2] = 32; + VectorCopy(pos,trend); + trend[2] += 28; + rs_trace = gi.trace (pos, trmin, trmax, trend,ent, tcontents ); + if(!rs_trace.allsolid && !rs_trace.startsolid && rs_trace.fraction == 1.0) + { + if(Bot_ExploAvoid(ent,pos)) + { + if(!CheckLaser(pos,trmax,trmin)) + return true; + } + return false; + } + if(Bot_ExploAvoid(ent,pos)) + { + if(!CheckLaser(pos,trmax,trmin)) + return 2; + } + return false; + + +/* trmax[2] = 32; + rs_trace = gi.trace (pos, trmin, trmax, pos,ent, tcontents ); + if(!rs_trace.allsolid && !rs_trace.startsolid) return true; + return 2;*/ + } + else //進めたとしても落ちたくない時のためのチェック + { + pos[0] = trstart[0]; + pos[1] = trstart[1]; + pos[2] = trstart[2]; + VectorCopy (trstart, trend); + + trstart[2] = trend[2] -8190; + rs_trace = gi.trace (trend, trmin, trmax, trstart,ent, tcontents | MASK_OPAQUE); + + *bottom = rs_trace.endpos[2] - ent->s.origin[2]; + + if(0/*rs_trace.fraction != 1.0 && rs_trace.plane.normal[2] < 0.7 && ent->waterlevel < 2 && ent->groundentity*/) + { + i = Get_vec_yaw (rs_trace.plane.normal,ryaw); + if( i < 90) + { + if(*bottom < 0 ) *bottom *= 3.0; + } + else + { +//if(ent->client->ps.pmove.pm_flags & PMF_DUCKED) gi.bprintf(PRINT_HIGH,"apoo2\n"); +//if(ent->client->zc.waterstate == WAS_FLOAT && ryaw == ent->client->zc.moveyaw) gi.bprintf(PRINT_HIGH,"apooX\n"); + return false; + } + } + if(0/*!ent->client->zc.route_trace*/) + { + if(rs_trace.plane.normal[2] > 0 && rs_trace.plane.normal[2] < 0.7) *bottom /= rs_trace.plane.normal[2]; + } +/* else + { + + }*/ + + contents = 0; + if(!ent->waterlevel) + { + if (ent->client->enviro_framenum > level.framenum) contents = CONTENTS_LAVA; + else contents = ( CONTENTS_LAVA | CONTENTS_SLIME); + } + if( rs_trace.contents & contents ) *bottom = -9999; /*return false;*/ + else if( rs_trace.surface->flags & SURF_SKY ) *bottom = -9999; + + if(!ent->waterlevel && (ent->client->ps.pmove.pm_flags & PMF_DUCKED) + && ent->groundentity == NULL && ent->velocity[2] > 10 && trmax[2] == 4) return 2; + + + if(trmax[2] == 32) + { + if(Bot_ExploAvoid(ent,pos)) + { + if(!CheckLaser(pos,trmax,trmin)) + return true; + } + return false; + } + +// trmax[2] = 32; + VectorCopy(pos,trend); + trend[2] += 28; + rs_trace = gi.trace (pos, trmin, trmax, trend,ent, tcontents ); + if(!rs_trace.allsolid && !rs_trace.startsolid && rs_trace.fraction == 1.0) + { + if(Bot_ExploAvoid(ent,pos)) + { + if(!CheckLaser(pos,trmax,trmin)) + return true; + } + return false; + } + if(Bot_ExploAvoid(ent,pos)) + { + if(!CheckLaser(pos,trmax,trmin)) + return 2; + } + return false; + } +} + +int Bot_Watermove ( edict_t *ent,vec3_t pos,float dist,float upd) +{ + trace_t rs_trace; + vec3_t trmin,trmax,touchmin; + float i,j; + + float max,vec; + + VectorCopy(ent->s.origin,trmax); + + trmax[2] += upd; + + rs_trace = gi.trace (ent->s.origin, ent->mins, ent->maxs, trmax,ent, /*MASK_BOTSOLID*/MASK_BOTSOLIDX); + + if(!rs_trace.allsolid && !rs_trace.startsolid ) + { + if(rs_trace.fraction > 0) + { + VectorCopy(rs_trace.endpos,pos); + return true; + if(upd < 0) ent->velocity[2] = 0; + } + } +//gi.bprintf(PRINT_HIGH,"Water MOVE NG %f %f!\n",dist,upd); +// return false; +// if(upd > -7 && upd < 7) return false; + + VectorCopy(ent->s.origin,trmin); + trmin[2] += upd; + + vec = -1; + max = 0; + for(i = 0;i < 360; i += 10) + { + if(i && upd > -13 && upd < 0/*13*/) break; + if(i > 60 && i < 300) continue; + + j = ent->client->zc.moveyaw + i; + + if(j > 180) j = j - 360; + else if(j < -180) j = j + 360; + else j = i; + + touchmin[0] = cos(j) * 24 ; + touchmin[1] = sin(j) * 24 ; + touchmin[2] = 0; + + VectorAdd(trmin,touchmin,trmax); + rs_trace = gi.trace (trmax/*ent->s.origin*/, ent->mins, ent->maxs, trmin,ent, MASK_BOTSOLIDX); + +// yaw = VectorLength(trmax); + if(!rs_trace.allsolid && !rs_trace.startsolid ) + { + VectorAdd(rs_trace.endpos,touchmin,trmax); + rs_trace = gi.trace (trmax, ent->mins, ent->maxs, trmax,ent, MASK_BOTSOLIDX); +//gi.bprintf(PRINT_HIGH,"NGAAAAAAAAAAAAAAAAAAAAAAAAAAA!\n"); + +// VectorSubtract(rs_trace.endpos,ent->s.origin,trmax); + if(!rs_trace.allsolid && !rs_trace.startsolid ) + { +//gi.bprintf(PRINT_HIGH,"go go go!\n"); + vec = i;break; + } + } + } + + if(vec == -1) + { +//gi.bprintf(PRINT_HIGH,"Water MOVE NG %f %f!\n",dist,upd); + return false; + } + +//gi.bprintf(PRINT_HIGH,"Water MOVE OK %f %f!\n",dist,upd); + VectorCopy(trmax,pos); + if(upd < 0) ent->velocity[2] = 0; + return true; + + touchmin[0] = cos(vec) * 16;//dist ; + touchmin[1] = sin(vec) * 16;//dist ; + touchmin[2] = 0; + + VectorAdd(ent->s.origin,touchmin,trmin); + VectorCopy(trmin,trmax); + trmax[2] += upd; + rs_trace = gi.trace (trmin, ent->mins, ent->maxs, trmax,ent, MASK_BOTSOLIDX); + + if(rs_trace.allsolid || rs_trace.startsolid ) + { + return false; + } + + VectorCopy(rs_trace.endpos,pos); + return true; + + + VectorCopy(rs_trace.plane.normal,trmin); + trmin[2] = 0; + VectorNormalize(trmin); + VectorAdd(trmax,trmin,trmax); + for(i = 1.0;i < dist;i += 1.0) + { + rs_trace = gi.trace (trmax, ent->mins, ent->maxs,trmax,ent, MASK_BOTSOLIDX/*MASK_PLAYERSOLID*/); + if(!rs_trace.allsolid && !rs_trace.startsolid) + { + VectorCopy(trmax,pos); + return true; + } + VectorAdd(trmax,trmin,trmax); + } +// gi.bprintf(PRINT_HIGH,"failed2\n"); + return false; +} + +int Bot_moveW ( edict_t *ent,float ryaw,vec3_t pos,float dist,float *bottom) +{ + float yaw; + vec3_t trstart,trend; + trace_t rs_trace; + + int contents; + + int tcontents; + + if (ent->client->enviro_framenum > level.framenum) contents = CONTENTS_LAVA; + else contents = ( CONTENTS_LAVA | CONTENTS_SLIME); + + tcontents = MASK_BOTSOLIDX/*MASK_PLAYERSOLID*/; + tcontents |= CONTENTS_WATER; + + //移動先がどうなっているのか調べる + yaw = ryaw*M_PI*2 / 360; + trend[0] = cos(yaw) * dist ; //start + trend[1] = sin(yaw) * dist ; + trend[2] = 0; + VectorAdd (trend, ent->s.origin, trstart); + + pos[0] = trstart[0]; + pos[1] = trstart[1]; + pos[2] = trstart[2]; + VectorCopy (trstart, trend); + + trstart[2] = trend[2] -8190;//95; + rs_trace = gi.trace (trend, ent->mins, ent->maxs, trstart,ent, tcontents); + + if((trend[2] - rs_trace.endpos[2]) >= 95) return false; + + if(rs_trace.contents & contents) return false; + if(!(rs_trace.contents & CONTENTS_WATER)) return false; + + *bottom = rs_trace.endpos[2] - ent->s.origin[2]; + return true; +} + +//----------------------------------------------------------------------------------------- +// Bank check +// true safe +// false danger +qboolean BankCheck(edict_t *ent,vec3_t pos) +{ + trace_t rs_trace; + vec3_t end,v1,v2; + + VectorSet(v1,-16,-16,-24); + VectorSet(v2,16,16,16); + + VectorCopy(pos,end); + + end[2] -= 5000; + + rs_trace = gi.trace (pos, v1, v2,end,ent, MASK_BOTSOLIDX/*MASK_PLAYERSOLID*/); + + if(rs_trace.startsolid || rs_trace.allsolid) return false; + if(rs_trace.plane.normal[2] < 0.8 ) return false; + return true; +} + + +//----------------------------------------------------------------------------------------- +// hazard check +// true safe +// false danger +qboolean HazardCheck(edict_t *ent,vec3_t pos) +{ + trace_t rs_trace; + vec3_t end,v1,v2; + int contents; + + VectorSet(v1,-16,-16,-16); + VectorSet(v2,16,16,16); + + VectorCopy(pos,end); + + end[2] -= 8190; + + if (ent->client->enviro_framenum > level.framenum) contents = CONTENTS_LAVA; + else contents = ( CONTENTS_LAVA | CONTENTS_SLIME); + + rs_trace = gi.trace (pos, v1, v2,end,ent, MASK_OPAQUE); + + if(rs_trace.contents & contents) return false; + return true; +} + +//----------------------------------------------------------------------------------------- +// bot's shot +/*qboolean Bot_Shot() +{ + + + if( ent->client->weaponstate == WEAPON_READY) + { + if(trent->movetype == MOVETYPE_STOP && trent->classname) + { + && trent->health && trent->moveinfo.state == 1 + && trent->takedamage)//trent->moveinfo.wait == 0) + { + trmax[0] = (trent->absmin[0] + trent->absmax[0]) / 2; + trmax[1] = (trent->absmin[1] + trent->absmax[1]) / 2; + trmax[2] = (trent->absmin[2] + trent->absmax[2]) / 2; + rs_trace = gi.trace (ent->s.origin, NULL, NULL, trmax ,ent, MASK_SHOT); + + if(rs_trace.ent == trent) + { + // gi.bprintf(PRINT_HIGH,"kkkkkkk\n"); + // continue; + VectorSubtract (trmax, ent->s.origin,trmin); + ent->client->ammo_index = 0; + ent->client->pers.weapon = FindItem("Blaster"); + ShowGun(ent); + ent->s.angles[YAW] = Get_yaw(trmin); + ent->s.angles[PITCH] = Get_pitch(trmin); + ent->client->buttons |= BUTTON_ATTACK; + ent->client->zc.objshot = true; + } + } + } + } + +*/ +//----------------------------------------------------------------------------------------- +// set the bot's combatstate + +void Set_Combatstate(edict_t *ent,int foundedenemy) +{ + vec3_t v; + gclient_t *client; + float distance; + edict_t *target; + int enewep; + int combskill; + float aim; + + client = ent->client; + + target = client->zc.first_target; + + if(client->zc.zcstate & STS_LADDERUP) return; + + if(target == NULL) + { + client->zc.zccmbstt &= ~CTS_COMBS; // clear status + return; + } + + //target is dead + if(!target->inuse || target->deadflag || target->solid != SOLID_BBOX) + { + client->zc.battleduckcnt = 0; + client->zc.first_target = NULL; + client->zc.zccmbstt &= ~CTS_COMBS; //clear status + + if((9 * random()) < Bot[client->zc.botindex].param[BOP_COMBATSKILL]) + UsePrimaryWeapon(ent); + return; + } + + if(!Bot_trace(ent,target)) + { + if(client->zc.targetlock <= level.time) + { + client->zc.first_target = NULL; + return; + } + client->zc.zccmbstt |= CTS_ENEM_NSEE;//can't see +// return; + } + else + { + ent->client->zc.targetlock = level.time + FRAMETIME * 12; + ent->client->zc.zccmbstt &= ~CTS_ENEM_NSEE;//can see + ent->client->zc.battlemode &= ~FIRE_ESTIMATE; + } + + VectorSubtract(target->s.origin,ent->s.origin,v); + distance = VectorLength(v); + + //enemy's weapon + enewep = Get_KindWeapon(target->client->pers.weapon); + + //status set + aim = 10.0 - (float)Bot[client->zc.botindex].param[BOP_AIM]; + if(aim <= 0 || aim > 10) aim = 5; + combskill = (int)Bot[client->zc.botindex].param[BOP_COMBATSKILL]; + if(combskill < 0 || combskill > 9) combskill = 5; + + + if(!(client->zc.zccmbstt & CTS_ENEM_NSEE)) Combat_Level0(ent,foundedenemy,enewep,aim,distance,combskill); + else if(client->zc.zccmbstt & FIRE_REFUGE) Combat_Level0(ent,foundedenemy,enewep,aim,distance,combskill); + else Combat_LevelX(ent,foundedenemy,enewep,aim,distance,combskill); + + if(client->zc.first_target) + { + client->zc.last_target = client->zc.first_target; + VectorCopy(client->zc.first_target->s.origin,client->zc.last_pos); + } + return; +} + +//----------------------------------------------------------------------------------------- +// Bot Jump +// return true sequaense done +// return false failed +qboolean Get_FlyingSpeed(float bottom,float block,float dist,float *speed) +{ + float tdist; + + if(bottom >= 40) + { + if(block > 4 ) return false; + tdist = (dist * block) / 4 ; + } + else if(bottom >= 35) + { + if(block > 5) return false; + tdist = (dist * block) / 5 ; + } + else if(bottom >= 30) + { + if(block > 6) return false; + tdist = (dist * block) / 6 ; + } + else if(bottom >= 20) + { + if(block > 7) return false; + tdist = (dist * block) / 7 ; + } + else if(bottom >= -5) + { + if(block > 8 ) return false; + tdist = (dist * block) / 8 ; + } + else if(bottom >= -20) + { + if(block > 9) return false; + tdist = (dist * block) / 7; + } + else if(bottom >= -35) + { + if(block > 10) return false; + tdist = (dist * block) / 6 ; + } + else if(bottom >= -52) + { + if(block > 11) return false; + tdist = (dist * block) / 5; + } + else if(bottom >= -75) + { + if(block > 12) return false; + tdist = (dist * block) / 4; + } + else if(bottom >= -95) + { + if(block > 13) return false; + tdist = (dist * block) / 3; + } + else if(bottom >= - 125) + { + if(block > 14) return false; + tdist = (dist * block) / 2; + } + else + { + if(block > 15) return false; + tdist = (dist * block) / 2; + } + + *speed = tdist / MOVE_SPD_RUN; + return true; +} + +qboolean Bot_Jump(edict_t *ent,vec3_t pos,float dist) +{ + float x,yaw,tdist,bottom,speed; + vec3_t temppos; + zgcl_t *zc; + + zc = &ent->client->zc; + + yaw = zc->moveyaw; + + Bot_moveT (ent,yaw,temppos,dist,&bottom); + if(bottom > -JumpMax) return false; + + for( x = 2 ; x <= 16; x += 1) + { + tdist = dist * x; + if( Bot_moveT (ent,yaw,temppos,tdist,&bottom) == true) + { + if( x == 2 && ( bottom > - JumpMax ) && bottom <= 0) + { + VectorCopy( pos,ent->s.origin); + return true; + } + if( bottom <= JumpMax && bottom > -JumpMax) + { + if(Get_FlyingSpeed(bottom,x,dist,&speed)) + { + speed *= 1.5; + if(speed > 1.2) speed = 1.2; + ent->moveinfo.speed = speed; + ent->velocity[2] += VEL_BOT_JUMP; + gi.sound(ent, CHAN_VOICE, gi.soundindex("*jump1.wav"), 1, ATTN_NORM, 0); + PlayerNoise(ent, ent->s.origin, PNOISE_SELF); //pon + Set_BotAnim(ent,ANIM_JUMP,FRAME_jump1-1,FRAME_jump6); +// ent->s.frame = FRAME_jump1-1; +// ent->client->anim_end = FRAME_jump6; +// ent->client->anim_priority = ANIM_JUMP; + return true; + } + } + continue; + } + else return false; + } + return false; +} + +//----------------------------------------------------------------------------------------- +// Bot Fall +// return true sequaense done +// return false failed + +qboolean Bot_Fall(edict_t *ent,vec3_t pos,float dist) +{ + zgcl_t *zc; + float x,l,speed,grav,vel,ypos,yori; + vec3_t v,vv; + int mf = false; + short mode = 0; + + zc = &ent->client->zc; + + if(zc->second_target != NULL)// && !(zc.zcstate & STS_COMBS)) + { + mode = 1; + ypos = zc->second_target->s.origin[2]; + + //if on hazard object cause error + if(!HazardCheck(ent,zc->second_target->s.origin)) + { + zc->second_target = NULL; + return false; + } + + yori = ent->s.origin[2]; + VectorSubtract(zc->second_target->s.origin,pos,v); + + grav = ent->gravity * sv_gravity->value * FRAMETIME; + if(v[2] > 0) goto JMPCHK; + + vel = ent->velocity[2]; +// grav = ent->gravity * sv_gravity->value * FRAMETIME; + l = 1.0; + for(x = 1;x <= FALLCHK_LOOPMAX;++x ,l += x ) + { + vel -= grav;// * l; + yori += vel * FRAMETIME; + if(ypos >= yori) + { + mf = true; + break; + } + } + VectorCopy(v,vv); + vv[2] = 0; + l = VectorLength(vv); + speed = l / x; + if(speed <= MOVE_SPD_RUN && mf) + { + ent->moveinfo.speed = speed / MOVE_SPD_RUN; + VectorCopy(pos,ent->s.origin); + return true; + } + goto JUMPCATCH; + } + else if(zc->route_trace) + { +//gi.bprintf(PRINT_HIGH,"fall\n"); + mode = 2; + Get_RouteOrigin(zc->routeindex,vv); + ypos = vv[2]; + + //if on hazard object cause error + if(!HazardCheck(ent,vv)) + { + if(++zc->routeindex >= CurrentIndex) zc->routeindex = 0; + +//gi.bprintf(PRINT_HIGH,"OFF 1\n"); //ppx +//gi.bprintf(PRINT_HIGH,"hazard out\n"); +// zc->route_trace = false; + return false; + } + + yori = pos[2]; + VectorSubtract(vv,pos,v); + + grav = ent->gravity * sv_gravity->value * FRAMETIME; + if(v[2] >= 0/*-8*/) goto JUMPCATCH;//JMPCHK; + + vel = ent->velocity[2]; + //grav = ent->gravity * sv_gravity->value * FRAMETIME; + l = 1.0; + for(x = 1;x <= FALLCHK_LOOPMAX;++x ,l += x ) + { + vel -= grav;// * l; + yori += vel * FRAMETIME; + if(ypos >= yori) + { + mf = true; + break; + } + } + + VectorCopy(v,vv); + vv[2] = 0; + //vel考慮の落下 + if(Route[zc->routeindex].state == GRS_ONTRAIN) + { + if(1/*Route[zc->routeindex].ent->trainteam == NULL*/) + { + vv[0] += FRAMETIME * Route[zc->routeindex].ent->velocity[0] * x; + vv[1] += FRAMETIME * Route[zc->routeindex].ent->velocity[1] * x; + } + } + + l = VectorLength(vv); + speed = l / x; + if(speed <= MOVE_SPD_RUN && mf) + { +//gi.bprintf(PRINT_HIGH,"fall do\n"); + ent->moveinfo.speed = speed / MOVE_SPD_RUN; + VectorCopy(pos,ent->s.origin); + return true; + } + goto JUMPCATCH; + } + goto JMPCHK; + +JUMPCATCH: + vel = ent->velocity[2] + VEL_BOT_JUMP; + yori = pos[2]; +// l = 1.0; +// VectorCopy(v,vv); +// vv[2] = 0; +// l = VectorLength(vv); +//gi.bprintf(PRINT_HIGH,"J fall\n"); + mf = false; + for(x = 1;x <= FALLCHK_LOOPMAX;++x /*,l += x*/ ) + { + vel -= grav; + yori += vel * FRAMETIME; + + if(vel > 0) + { + if(mf == false) + { + if(ypos < yori) mf = 2; +//gi.bprintf(PRINT_HIGH,"pre ok\n"); + } +/* else if(mf == 2) + { + if(ypos >= yori) + if((l / x) < MOVE_SPD_RUN) + { + mf = true; + break; + } + }*/ + } + else if(x > 1) + { +//gi.bprintf(PRINT_HIGH,"oops\n"); + if(mf == false) + { + if(ypos < yori) mf = 2; + } + + else if(mf == 2) + { + if(ypos >= yori) + { +/* if((l / (x - 1)) <= MOVE_SPD_RUN) + {*/ +//gi.bprintf(PRINT_HIGH,"Go %f\n",l / x); + mf = true; + break; +// } + } + } + } + } + VectorCopy(v,vv); + vv[2] = 0; + if(mode == 2) + { + //vel考慮の落下 + if(Route[zc->routeindex].state == GRS_ONTRAIN) + { + if(1/*Route[zc->routeindex].ent->trainteam == NULL*/) + { +//gi.bprintf(PRINT_HIGH,"Go!\n"); //ppx + vv[0] += FRAMETIME * Route[zc->routeindex].ent->velocity[0] * x; + vv[1] += FRAMETIME * Route[zc->routeindex].ent->velocity[1] * x; + } + } + } + l = VectorLength(vv); + + if(x > 1) l = l / (x - 1); + if(l < MOVE_SPD_RUN && mf == true) + { + ent->moveinfo.speed = l / MOVE_SPD_RUN; + VectorCopy(pos,ent->s.origin); + + ent->velocity[2] += VEL_BOT_JUMP; + gi.sound(ent, CHAN_VOICE, gi.soundindex("*jump1.wav"), 1, ATTN_NORM, 0); + PlayerNoise(ent, ent->s.origin, PNOISE_SELF); //pon + Set_BotAnim(ent,ANIM_JUMP,FRAME_jump1-1,FRAME_jump6); +// ent->s.frame = FRAME_jump1-1; +// ent->client->anim_end = FRAME_jump6; +// ent->client->anim_priority = ANIM_JUMP; +//gi.bprintf(PRINT_HIGH,"j fall do\n"); + return true; + } + + if(mode == 1) goto JMPCHK;//zc->second_target = NULL; +//ponko else zc->route_trace = false; +//gi.bprintf(PRINT_HIGH,"j fall false\n"); +// return false; +JMPCHK: +//gi.bprintf(PRINT_HIGH,"NJ \n"); + if(Bot_Jump(ent,pos,dist)) return true; + +//gi.bprintf(PRINT_HIGH,"NJ FAIL\n"); + zc->second_target = NULL; + return false; +} +//----------------------------------------------------------------------------------------- +// target jump + +qboolean TargetJump(edict_t *ent,vec3_t tpos) +{ + zgcl_t *zc; + float x,l,grav,vel,ypos,yori; + vec3_t v,vv; + int mf = false; + + zc = &ent->client->zc; + grav = ent->gravity * sv_gravity->value * FRAMETIME; + + vel = ent->velocity[2] + VEL_BOT_JUMP; + yori = ent->s.origin[2]; + ypos = tpos[2]; + + //if on hazard object cause error + if(!HazardCheck(ent,tpos)) return false; + + VectorSubtract(tpos,ent->s.origin,v); + + for(x = 1;x <= FALLCHK_LOOPMAX * 2 ;++x ) + { + vel -= grav; + yori += vel * FRAMETIME; + + if(vel > 0) + { + if(mf == false) + { + if(ypos < yori) mf = 2; + } + } + else if(x > 1) + { + if(mf == false) + { + if(ypos < yori) mf = 2; + } + + else if(mf == 2) + { + if(ypos >= yori) + { + mf = true; + break; + } + } + } + } + VectorCopy(v,vv); + vv[2] = 0; + + l = VectorLength(vv); + + if(x > 1) l = l / (x - 1); + if(l < MOVE_SPD_RUN && mf == true) + { + ent->moveinfo.speed = l / MOVE_SPD_RUN; + + ent->velocity[2] += VEL_BOT_JUMP; + gi.sound(ent, CHAN_VOICE, gi.soundindex("*jump1.wav"), 1, ATTN_NORM, 0); + PlayerNoise(ent, ent->s.origin, PNOISE_SELF); //pon + Set_BotAnim(ent,ANIM_JUMP,FRAME_jump1-1,FRAME_jump6); + return true; + } + return false; +} + +qboolean TargetJump_Turbo(edict_t *ent,vec3_t tpos) +{ + zgcl_t *zc; + float x,l,grav,vel,ypos,yori; + vec3_t v,vv; + int mf = false; + float jvel; + + zc = &ent->client->zc; + grav = ent->gravity * sv_gravity->value * FRAMETIME; + + vel = ent->velocity[2] + VEL_BOT_JUMP; + +//if(vel > (VEL_BOT_JUMP + 100 + ent->gravity * sv_gravity->value * FRAMETIME )) +// vel = VEL_BOT_JUMP + 100 + ent->gravity * sv_gravity->value * FRAMETIME; + jvel = vel; + + yori = ent->s.origin[2]; + ypos = tpos[2]; + + //if on hazard object cause error + if(!HazardCheck(ent,tpos)) return false; + + VectorSubtract(tpos,ent->s.origin,v); + + for(x = 1;x <= FALLCHK_LOOPMAX * 2 ;++x ) + { + vel -= grav; + yori += vel * FRAMETIME; + + if(vel > 0) + { + if(mf == false) + { + if(ypos < yori) mf = 2; + } + } + else if(x > 1) + { + if(mf == false) + { + if(ypos < yori) mf = 2; + } + + else if(mf == 2) + { + if(ypos >= yori) + { + mf = true; + break; + } + } + } + } + VectorCopy(v,vv); + vv[2] = 0; + + l = VectorLength(vv); + + if(x > 1) l = l / (x - 1); + if(l < MOVE_SPD_RUN && mf == true) + { + ent->moveinfo.speed = l / MOVE_SPD_RUN; +// VectorCopy(pos,ent->s.origin); + + ent->velocity[2] = jvel;//VEL_BOT_JUMP; + gi.sound(ent, CHAN_VOICE, gi.soundindex("*jump1.wav"), 1, ATTN_NORM, 0); + PlayerNoise(ent, ent->s.origin, PNOISE_SELF); //pon + Set_BotAnim(ent,ANIM_JUMP,FRAME_jump1-1,FRAME_jump6); +// ent->s.frame = FRAME_jump1-1; +// ent->client->anim_end = FRAME_jump6; +// ent->client->anim_priority = ANIM_JUMP; + return true; + } + return false; +} +qboolean TargetJump_Chk(edict_t *ent,vec3_t tpos,float defvel) +{ + zgcl_t *zc; + float x,l,grav,vel,ypos,yori; + vec3_t v,vv; + int mf = false; + + zc = &ent->client->zc; + grav = ent->gravity * sv_gravity->value * FRAMETIME; + + vel = defvel + VEL_BOT_JUMP; + yori = ent->s.origin[2]; + ypos = tpos[2]; + + //if on hazard object cause error + if(!HazardCheck(ent,tpos)) return false; + + VectorSubtract(tpos,ent->s.origin,v); + + for(x = 1;x <= FALLCHK_LOOPMAX * 2 ;++x ) + { + vel -= grav; + yori += vel * FRAMETIME; + + if(vel > 0) + { + if(mf == false) + { + if(ypos < yori) mf = 2; + } + } + else if(x > 1) + { + if(mf == false) + { + if(ypos < yori) mf = 2; + } + + else if(mf == 2) + { + if(ypos >= yori) + { + mf = true; + break; + } + } + } + } + VectorCopy(v,vv); + vv[2] = 0; + + l = VectorLength(vv); + + if(x > 1) l = l / (x - 1); + if(l < MOVE_SPD_RUN && mf == true) + { + return true; + } + return false; +} +//----------------------------------------------------------------------------------------- +// set anim +void Set_BotAnim(edict_t *ent,int anim,int frame,int end) +{ + if(ent->client->anim_priority < anim) + { + ent->s.frame = frame; + ent->client->anim_end = end; + } +} + + +//----------------------------------------------------------------------------------------- +// Get Water State +void Get_WaterState(edict_t *ent) +{ + zgcl_t *zc; + vec3_t trmin,trmax; + float x; + trace_t rs_trace; + + zc = &ent->client->zc; + + //--------------- + //get waterstate + if(ent->waterlevel) + { + VectorCopy(ent->s.origin,trmax); + VectorCopy(ent->s.origin,trmin); + trmax[2] -= 24; + trmin[2] += 8; + + rs_trace = gi.trace (trmin, NULL,NULL,trmax,ent, MASK_WATER ); + x = trmin[2] - rs_trace.endpos[2]; + + if(rs_trace.allsolid || rs_trace.startsolid || (/*x >= 4 &&*/ x < 4.0 )) zc->waterstate = WAS_IN; + else + { + + if(x >= 4.0 && x <= 12.0 ) zc->waterstate = WAS_FLOAT; + else zc->waterstate = WAS_NONE; + } + } + else zc->waterstate = WAS_NONE; +} + + +//----------------------------------------------------------------------------------------- +// Get origin of Route Index +void Get_RouteOrigin(int index,vec3_t pos) +{ + edict_t *e; + + //when normal or items + if(Route[index].state <= GRS_ITEMS || Route[index].state >= GRS_GRAPSHOT) + { + if(Route[index].state == GRS_ITEMS) + { + VectorCopy(Route[index].ent->s.origin,pos); + pos[2] += 8; + } + else VectorCopy(Route[index].Pt,pos); + + } + //when plat + else if(Route[index].state == GRS_ONPLAT) + { + VectorCopy(Route[index].ent->union_ent->s.origin,pos); + pos[2] += 8; + } + //when train + else if(Route[index].state == GRS_ONTRAIN) + { + if(Route[index].ent->trainteam == NULL) + { + VectorCopy(Route[index].ent->union_ent->s.origin,pos); + pos[2] += 8; + return; + } + if(Route[index].ent->target_ent) + { + if(VectorCompare(Route[index].Tcourner,Route[index].ent->target_ent->s.origin)) + { + VectorCopy(Route[index].ent->union_ent->s.origin,pos); + pos[2] += 8; + return; + } + } + e = Route[index].ent->trainteam; + while(1) + { + if(e == Route[index].ent) break; + if(e->target_ent) + { + if(VectorCompare(Route[index].Tcourner,e->target_ent->s.origin)) + { + VectorCopy(e->union_ent->s.origin,pos); + pos[2] += 8; + Route[index].ent = e; + return; + } + } + e = e->trainteam; + } + VectorCopy(Route[index].ent->union_ent->s.origin,pos); + pos[2] += 8; + return; + } + else if(Route[index].state == GRS_ONDOOR) + { + if(Route[index].ent->union_ent) + { + VectorCopy(Route[index].ent->union_ent->s.origin,pos); + pos[2] += 8; + } + else if(index + 1 < CurrentIndex) + { + if(Route[index + 1].state <= GRS_ITEMS ) + { + VectorCopy(Route[index + 1].Pt,pos); + if(Route[index + 1].state == GRS_ITEMS) pos[2] += 8; + pos[2] += 8; + } + //when plat or train + else if(Route[index + 1].state <= GRS_ONTRAIN) + { + VectorCopy(Route[index + 1].ent->union_ent->s.origin,pos); + pos[2] += 8; + } + else if(Route[index + 1].state == GRS_PUSHBUTTON) + { + VectorCopy(Route[index + 1].ent->union_ent->s.origin,pos); + pos[2] += 8; + } + else VectorCopy(Route[index + 1].Pt,pos); + } + else + { + pos[0] = (Route[index].ent->absmin[0] + Route[index].ent->absmax[0]) / 2; + pos[1] = (Route[index].ent->absmin[1] + Route[index].ent->absmax[1]) / 2; + pos[2] = Route[index].ent->absmax[2]; + } + } + else if(Route[index].state == GRS_PUSHBUTTON) VectorCopy(Route[index].ent->union_ent->s.origin,pos); +} + +//----------------------------------------------------------------------------------------- +// search nearly pod +void Search_NearlyPod(edict_t *ent) +{ + vec3_t v,v1,v2; + float x; + + if(Route[ent->client->zc.routeindex].state >= GRS_ITEMS) return; +// else if(Route[ent->client->zc.routeindex].state ==/*>=*/ GRS_ITEMS) +// { +// if(Route[ent->client->zc.routeindex].ent->solid != SOLID_TRIGGER) return; +// } + + if((ent->client->zc.routeindex + 1) < CurrentIndex) + { + if(Route[ent->client->zc.routeindex + 1].state >= GRS_ITEMS) return; + Get_RouteOrigin(ent->client->zc.routeindex + 1,v); + if(TraceX(ent,v)) + { + VectorSubtract(v,ent->s.origin,v1); + + Get_RouteOrigin(ent->client->zc.routeindex,v); + VectorSubtract(v,ent->s.origin,v2); + x = fabs(v1[2]); + + if(VectorLength(v1) < VectorLength(v2) && x <= JumpMax + && Route[ent->client->zc.routeindex].state <= GRS_ONROTATE) + { + ent->client->zc.routeindex++; + } + else if(ent->client->zc.waterstate) return; + else if(v2[2] > JumpMax && fabs(v1[2]) < JumpMax) ent->client->zc.routeindex++; + + } + } +} + +int Get_KindWeapon(gitem_t *it) +{ + if(it == NULL) return WEAP_BLASTER; + + if(it->weaponthink == Weapon_Shotgun) return WEAP_SHOTGUN; + else if(it->weaponthink == Weapon_SuperShotgun) return WEAP_SUPERSHOTGUN; + else if(it->weaponthink == Weapon_Machinegun) return WEAP_MACHINEGUN; + else if(it->weaponthink == Weapon_Chaingun) return WEAP_CHAINGUN; + else if(it->weaponthink == Weapon_Grenade) return WEAP_GRENADES; + else if(it->weaponthink == Weapon_Trap) return WEAP_TRAP; + else if(it->weaponthink == Weapon_GrenadeLauncher) return WEAP_GRENADELAUNCHER; + else if(it->weaponthink == Weapon_RocketLauncher) return WEAP_ROCKETLAUNCHER; + else if(it->weaponthink == Weapon_HyperBlaster) return WEAP_HYPERBLASTER; + else if(it->weaponthink == Weapon_Ionripper) return WEAP_BOOMER; + else if(it->weaponthink == Weapon_Railgun) return WEAP_RAILGUN; + else if(it->weaponthink == Weapon_Phalanx) return WEAP_PHALANX; + else if(it->weaponthink == Weapon_BFG) return WEAP_BFG; + else if(it->weaponthink == CTFWeapon_Grapple) return WEAP_GRAPPLE; + else return WEAP_BLASTER; +} + +//----------------------------------------------------------------------------------------- +// +// +// +// normal bot's AI +// +// +// +//----------------------------------------------------------------------------------------- + +void Bots_Move_NORM (edict_t *ent) +{ + float dist; //moving distance + zgcl_t *zc; //zc's address + + int foundedenemy; + + gitem_t *item; + + float x,yaw,iyaw,f1,f2,f3,bottom; + qboolean tempflag;//,buttonuse; + vec3_t temppos; + + trace_t rs_trace; + edict_t *touch[MAX_EDICTS],*trent; + vec3_t touchmin,touchmax,v,vv; + vec3_t trmin,trmax; + int i,j,k; + qboolean canrocj,waterjumped; + edict_t *it_ent; + gitem_t *it; + + edict_t *front,*left,*right,*e; + + char *string; + + cplane_t plane; + + vec3_t Origin,Velocity;//original param + float OYaw; // + + qboolean ladderdrop; + + + trace_priority = TRP_NORMAL; //trace on + + zc = &ent->client->zc; //client add + + ent->client->zc.objshot = false; //object shot clear + + ent->client->buttons &= ~BUTTON_ATTACK; + + //-------------------------------------------------------------------------------------- + //Solid Check + i = gi.pointcontents (ent->s.origin); + if(i & CONTENTS_SOLID) + T_Damage (ent, ent, ent, ent->s.origin, ent->s.origin, ent->s.origin,100 , 1, 0, MOD_CRUSH); + + + if(VectorCompare(ent->s.origin,ent->s.old_origin)) + { + if(ent->groundentity == NULL && !ent->waterlevel) + { + VectorCopy(ent->s.origin,v); + v[2] -= 1.0; + rs_trace = gi.trace(ent->s.origin,ent->mins,ent->maxs,v,ent,MASK_BOTSOLIDX); + if(!rs_trace.allsolid && !rs_trace.startsolid) ent->groundentity = rs_trace.ent; + } + } + // VectorCopy(ent->s.origin,Origin); +// VectorCopy(ent->velocity,Velocity); +// OYaw = ent->s.angles[YAW]; + + //-------------------------------------------------------------------------------------- + //Check Debug mode + if(chedit->value) + { + j = false; + if(!zc->route_trace ) + { +//gi.bprintf(PRINT_HIGH,"route off\n"); + j = true; + } + if(zc->routeindex >= CurrentIndex) + { +//gi.bprintf(PRINT_HIGH,"index overflow\n"); + j = true; + } + else if(Route[zc->routeindex].index == 0 && zc->routeindex > 0) + { +//gi.bprintf(PRINT_HIGH,"index end\n"); + j = true; + } + + if(j) + { + RemoveBot(); + //gi.cprintf(NULL,PRINT_HIGH,"Tracing failed.\n"); + return; + } + } + //-------------------------------------------------------------------------------------- + //get JumpMax + if(JumpMax == 0) + { + x = /*ent->velocity[2] + */ VEL_BOT_JUMP - ent->gravity * sv_gravity->value * FRAMETIME; + JumpMax = 0; + while(1) + { + JumpMax += x * FRAMETIME; + x -= ent->gravity * sv_gravity->value * FRAMETIME; + if( x < 0 ) break; + } +//gi.bprintf(PRINT_HIGH,"JumpMax %f",JumpMax); + } +/* if(!JmpTableChk) + { + + + }*/ + //-------------------------------------------------------------------------------------- + //target set + if(!zc->havetarget && zc->route_trace) + { + k = 0; + //primary weapon + j = mpindex[Bot[zc->botindex].param[BOP_PRIWEP]]; + //secondary weapon +// if(j && ent->client->pers.inventory[j]) j = mpindex[Bot[zc->botindex].param[BOP_SECWEP]]; + + //ctf + if(0/*ctf->value && bot_team_flag1 && bot_team_flag2*/) + { + it = NULL; + if(ent->client->resp.ctf_team == CTF_TEAM1) + { + if(zc->ctfstate == CTFS_DEFENDER || zc->ctfstate == CTFS_CARRIER) it = bot_team_flag1->item; + else if(zc->ctfstate == CTFS_OFFENCER) it = bot_team_flag2->item; + } + else if(ent->client->resp.ctf_team == CTF_TEAM2) + { + if(zc->ctfstate == CTFS_DEFENDER || zc->ctfstate == CTFS_CARRIER) it = bot_team_flag2->item; + else if(zc->ctfstate == CTFS_OFFENCER) it = bot_team_flag1->item; + } + if(it) {k = true;} + } +if(ctf->value) j = 0; + if((j && !ent->client->pers.inventory[j]) || k) + { + if(!k) it = &itemlist[j]; + if(zc->targetindex < zc->routeindex + || zc->targetindex >= CurrentIndex) zc->targetindex = zc->routeindex; + for(i = zc->targetindex + 1;i < (zc->targetindex + 50);i++) + { + if(i > CurrentIndex) break; + if(Route[i].state == GRS_ITEMS) + { + if(Route[i].ent->item == it) + { +//gi.bprintf(PRINT_HIGH,"Target Flag On\n"); + zc->havetarget = true; + break; + } + else if(!ctf->value && Route[i].ent->solid == SOLID_TRIGGER) + { + //Quad + if(j = mpindex[MPI_QUAD]) + if(Route[i].ent->item == &itemlist[j]) + {zc->havetarget = true; break;} + //Quad fire + if(j = mpindex[MPI_QUADF]) + if(Route[i].ent->item == &itemlist[j]) + {zc->havetarget = true; break;} + //Quad fire + if(j = mpindex[MPI_PENTA]) + if(Route[i].ent->item == &itemlist[j]) + {zc->havetarget = true; break;} + } + } + } + zc->targetindex = i; + } + else + { + //quad + j = mpindex[MPI_QUAD]; + //quad fire + if(!j) j = mpindex[MPI_QUADF]; + + if(j) + { + it = &itemlist[j]; + if(zc->targetindex < zc->routeindex + || zc->targetindex >= CurrentIndex) zc->targetindex = zc->routeindex; + for(i = zc->targetindex + 1;i < (zc->targetindex + 25);i++) + { + if(i > CurrentIndex) break; + if(Route[i].state == GRS_ITEMS) + { + if(Route[i].ent->item == it) + { + if(Route[i].ent->solid == SOLID_TRIGGER) + { + zc->havetarget = true; + break; + } + } + } + } + zc->targetindex = i; + } + } + } + else if(zc->havetarget) + { + if(zc->targetindex < zc->routeindex) + { + zc->havetarget = false; + zc->targetindex = zc->routeindex; + } + + else if(ctf->value) + { + it = NULL; + if(ent->client->resp.ctf_team == CTF_TEAM1) + { + if(zc->ctfstate == CTFS_DEFENDER || zc->ctfstate == CTFS_CARRIER) it = bot_team_flag1->item; + else if(zc->ctfstate == CTFS_OFFENCER) it = bot_team_flag2->item; + } + else if(ent->client->resp.ctf_team == CTF_TEAM2) + { + if(zc->ctfstate == CTFS_DEFENDER || zc->ctfstate == CTFS_CARRIER) it = bot_team_flag2->item; + else if(zc->ctfstate == CTFS_OFFENCER) it = bot_team_flag1->item; + } + if(Route[zc->targetindex].ent->item != it) + { + zc->havetarget = false; + zc->targetindex = zc->routeindex; + } + } + } + //-------------------------------------------------------------------------------------- + //can rocket jump? + it = Fdi_ROCKETLAUNCHER;//FindItem("Rocket Launcher"); + i = ITEM_INDEX(Fdi_ROCKETS/*FindItem("Rockets")*/); + + if( ent->client->pers.inventory[ITEM_INDEX(it)] + && ent->client->pers.inventory[i] > 0) canrocj = true; + else canrocj = false; + if(!Bot[zc->botindex].param[BOP_ROCJ]) canrocj = false; + + //-------------------------------------------------------------------------------------- + //ducking check + if(ent->client->ps.pmove.pm_flags & PMF_DUCKED) + { + if(ent->client->zc.battleduckcnt > 0 && ent->groundentity) goto DCHCANC; + + VectorSet(v,16,16,32); + VectorCopy(ent->s.origin,v); + + v[2] += 28; + + rs_trace = gi.trace(ent->s.origin,ent->mins,ent->maxs,v,ent,MASK_BOTSOLIDX); +//gi.bprintf(PRINT_HIGH,"try to duck clear!\n"); + if(!rs_trace.startsolid && !rs_trace.allsolid && rs_trace.fraction == 1.0) + { +//gi.bprintf(PRINT_HIGH,"duck cleared!\n"); + ent->client->ps.pmove.pm_flags &= ~PMF_DUCKED; + ent->maxs[2] = 32; + } +// else gi.bprintf(PRINT_HIGH,"failed %i %i\n",rs_trace.startsolid ,rs_trace.allsolid); + } + else if(ent->velocity[2] > 10 && ent->groundentity == NULL + && !(zc->zcstate & STS_SJMASK)) + { + VectorSet(v,16,16,40); + rs_trace = gi.trace(ent->s.origin,ent->mins,v,ent->s.origin,ent,MASK_BOTSOLIDX); + + if(rs_trace.startsolid || rs_trace.allsolid) + { + ent->client->ps.pmove.pm_flags |= PMF_DUCKED; + ent->maxs[2] = 4; + } + } +DCHCANC://しゃがみっぱなし + //-------------------------------------------------------------------------------------- + //movingspeed set + if(ent->groundentity || ent->waterlevel) + { + if(ent->waterlevel) + { + if(!(zc->zcstate & STS_WATERJ)) zc->zcstate &= ~STS_SJMASK; + } + else zc->zcstate &= ~STS_SJMASK; + if(ent->groundentity && !ent->waterlevel) ent->moveinfo.speed = 1.0; + else if(ent->waterlevel && ent->velocity[2] <= 1) ent->moveinfo.speed = 1.0; + } + + // if ducking down to da speed + if(ent->client->ps.pmove.pm_flags & PMF_DUCKED && ent->groundentity) dist = MOVE_SPD_DUCK * ent->moveinfo.speed; + else + { + if( !ent->waterlevel ) + { + if(chedit->value || !Bot[zc->botindex].param[BOP_WALK] || !ent->groundentity) dist = MOVE_SPD_RUN * ent->moveinfo.speed; + else dist = MOVE_SPD_WALK * ent->moveinfo.speed; + } + else + { + if(ent->groundentity && ent->waterlevel < 2 ) dist = MOVE_SPD_RUN * ent->moveinfo.speed; + else dist = MOVE_SPD_WATER * ent->moveinfo.speed; + } + if(ent->groundentity) dist *= zc->ground_slope; + } + + //-------------------------------------------------------------------------------------- + //get waterstate + Get_WaterState( ent ); + + //-------------------------------------------------------------------------------------- + // + //search for enemy + // +// foundedenemy = 0; +// i = CTS_AIMING ; + + zc->firstinterval += 2; + if(zc->firstinterval >= 10) + { + zc->foundedenemy = Bot_SearchEnemy(ent); + zc->firstinterval = Bot[zc->botindex].param[BOP_REACTION]; + if(zc->firstinterval > 10) zc->firstinterval = 10; + if(zc->firstinterval < 0) zc->firstinterval = 0; + } + //-------------------------------------------------------------------------------------- + // + //bot's combat status set + // + foundedenemy = zc->foundedenemy; +// if(ent->client->ctf_grapple && !(ent->client->buttons & BUTTON_ATTACK)) {} +// else + Set_Combatstate(ent,foundedenemy); + if(trace_priority == TRP_ALLKEEP) goto VCHCANSEL; + //-------------------------------------------------------------------------------------- + //brause target status + if(zc->second_target != NULL && zc->route_trace) + zc->rt_locktime = level.time + FRAMETIME * POD_LOCKFRAME; + if(zc->second_target != NULL && !(zc->zcstate & STS_WAITSMASK)) + { + if(zc->second_target->solid != SOLID_TRIGGER || !zc->second_target->inuse) + { + zc->second_target = NULL; + } + else if(!Bot_trace (ent,zc->second_target)) + { + zc->second_target = NULL; + } + else if((zc->second_target->s.origin[2] - ent->s.origin[2]) > 32 && zc->waterstate != WAS_IN) + { + VectorSubtract(zc->second_target->s.origin,ent->s.origin,temppos); + x = zc->second_target->moveinfo.start_origin[2] - ent->s.origin[2]; + k = false; + if(temppos[2] > 32) + { + if(!canrocj) + { + if(x < 0 || x > 32) k = true; + else if(!Bot_trace2 (ent,zc->second_target->moveinfo.start_origin)) k = true; + } + else + { + if(temppos[2] > 300) k = true; + } + } + else + { + if(temppos[0] <= (ent->absmax[0] + 32) && temppos[0] >= (ent->absmin[0] + 32)) + if(temppos[1] <= (ent->absmax[1] + 32) && temppos[1] >= (ent->absmin[1] + 32)) + if(temppos[2] <= (ent->absmax[2] + 32) && temppos[2] >= (ent->absmin[2] + 32)) + k = true; + } + if(k) + { + zc->second_target = NULL; +// if(zc->route_trace) Search_NearlyPod(ent); + } + } + } +// if(zc->route_trace && (zc->zcstate & STS_LADDERUP)) zc->rt_locktime = level.time + FRAMETIME * POD_LOCKFRAME; + if(zc->route_trace) + { +//PON-CTF>> + if(zc->routeindex > 0) + { + if(ent->client->ctf_grapple) + { + ent->client->buttons |= BUTTON_ATTACK; + } + +// if(Route[zc->routeindex].state == GRS_GRAPSHOT) +// { +// if(Route[zc->routeindex - 1].state == GRS_GRAPRELEASE) zc->routeindex++; +// } + if(!zc->first_target && ent->client->pers.weapon != Fdi_GRAPPLE ) + { + for(i = 0;i < (5 * 2);i++) + { + if((zc->routeindex + i) >= CurrentIndex) break; + if(Route[zc->routeindex + i].state == GRS_GRAPSHOT) + { + item = Fdi_GRAPPLE;//FindItem("Grapple"); + if( ent->client->pers.inventory[ITEM_INDEX(item)]) item->use(ent,item); + } + } + } + //撃てGrapple + else if(Route[zc->routeindex - 1].state == GRS_GRAPSHOT + && ent->client->ctf_grapple == NULL + && zc->first_target == NULL) + { + item = Fdi_GRAPPLE;;//FindItem("Grapple"); + + if( ent->client->pers.inventory[ITEM_INDEX(item)]) + { + item->use(ent,item); +// ent->client->ctf_grapplestate = CTF_GRAPPLE_STATE_FLY; +// ent->client->pers.weapon = item; +// ent->client->weaponstate = WEAPON_READY; +// ent->s.sound = 0; + ShowGun(ent); + if(ent->client->weaponstate == WEAPON_READY && ent->client->pers.weapon == item) + { + vv[0] = ent->s.origin[0]; + vv[1] = ent->s.origin[1]; + vv[2] = ent->s.origin[2] + ent->viewheight-8+2; +// VectorCopy(Route[zc->routeindex - 1].Pt,ent->s.origin); + VectorSubtract(Route[zc->routeindex - 1].Tcourner,vv,v); + ent->s.angles[YAW] = Get_yaw(v); + ent->s.angles[PITCH] = Get_pitch(v); + trace_priority = TRP_ANGLEKEEP; +// item->use(ent,item); +// if(ent->client->weaponstate == WEAPON_READY) +// { + ent->client->buttons |= BUTTON_ATTACK; +// } + } + else + { + if(zc->first_target == NULL && ent->groundentity) trace_priority = TRP_ALLKEEP; + zc->routeindex--;/* trace_priority = TRP_ALLKEEP;*/ + } + } + } + else if(ent->client->ctf_grapple) + { + //sticking check + if(zc->nextcheck < (level.time + FRAMETIME * 10)) + { + VectorSubtract(zc->pold_origin,ent->s.origin,temppos); + if(VectorLength(temppos) < 64) + { + if(zc->route_trace) + { + zc->route_trace = false; + zc->routeindex++; + ent->client->buttons &= ~BUTTON_ATTACK; + } + } + + if(zc->nextcheck < level.time) + { + VectorCopy(ent->s.origin,zc->pold_origin); + zc->nextcheck = level.time + FRAMETIME * 40; + } + } + if(ent->client->ctf_grapplestate == CTF_GRAPPLE_STATE_PULL) + { + if(ent->groundentity == NULL) zc->zcstate |= STS_ROCJ; + + if(Route[zc->routeindex].state != GRS_GRAPRELEASE) + { + for(i = 0;(zc->routeindex - i) > 0;i++) + { + if(Route[zc->routeindex - i].state == GRS_GRAPSHOT) break; + } + if((zc->routeindex - i) > 0) + { + for(j = 0;(zc->routeindex - i + j) < CurrentIndex;j++) + { + if(Route[zc->routeindex - i + j].state == GRS_GRAPRELEASE) break; + } + if((zc->routeindex - i + j) < CurrentIndex) + zc->routeindex = zc->routeindex - i + j; + } + } + if(Route[zc->routeindex].state != GRS_GRAPRELEASE) + { + item = Fdi_GRAPPLE;//FindItem("Grapple"); + item->use(ent,item); +// ent->client->pers.weapon = item; +// ent->s.sound = 0; +// ShowGun(ent); + ent->client->buttons &= ~BUTTON_ATTACK; + } + } + else if(ent->client->ctf_grapplestate == CTF_GRAPPLE_STATE_HANG + && Route[zc->routeindex].state != GRS_GRAPRELEASE) + { + item = Fdi_GRAPPLE;//FindItem("Grapple"); + item->use(ent,item); +// ent->client->pers.weapon = item; +// ent->s.sound = 0; +// ShowGun(ent); + ent->client->buttons &= ~BUTTON_ATTACK; +//gi.bprintf(PRINT_HIGH,"Groff 2!\n"); + } + } + + else if(Route[zc->routeindex - 1].state == GRS_GRAPHOOK + && ent->client->ctf_grapple) + { + if(ent->client->ctf_grapplestate == CTF_GRAPPLE_STATE_FLY) + trace_priority = TRP_ALLKEEP; + + } +/* if(Route[zc->routeindex].state == GRS_GRAPHOOK + && ent->client->ctf_grapple) + { + trace_priority = TRP_ALLKEEP; + }*/ + if(Route[zc->routeindex].state == GRS_GRAPRELEASE + && ent->client->ctf_grapple) + { + k = 0; + if(ent->client->ctf_grapplestate == CTF_GRAPPLE_STATE_FLY) + trace_priority = TRP_ALLKEEP; + else if(ent->client->ctf_grapplestate == CTF_GRAPPLE_STATE_PULL) + { + if(1) + { + e = (edict_t*)ent->client->ctf_grapple; + VectorSubtract(ent->s.origin,e->s.origin,v); + yaw = VectorLength(v); + if(yaw <= (Route[zc->routeindex].Tcourner[0] /*+ 32*/)) + { +// if(yaw < 40) ent->moveinfo.speed = 0; + item = Fdi_GRAPPLE;//FindItem("Grapple"); + item->use(ent,item); + ent->client->buttons &= ~BUTTON_ATTACK; + zc->routeindex++; + k = true; +//gi.bprintf(PRINT_HIGH,"Groff 1!\n"); + } + else if(!ent->waterlevel) trace_priority = TRP_ALLKEEP; + } +// gi.bprintf(PRINT_HIGH,"length %f < %f\n",Route[zc->routeindex].Tcourner[0],VectorLength(v)); + } + else if(ent->client->ctf_grapplestate == CTF_GRAPPLE_STATE_HANG) + { +/* if((zc->routeindex + 1) < CurrentIndex) + { + if(Route[zc->routeindex + 1].state == GRS_GRAPSHOT) + { + if(TraceX(ent,Route[zc->routeindex + 1].Tcourner)) k = true; + } + else k = true; + }*/ + ent->moveinfo.speed = 0; + k = true; + if(k) + { + item = Fdi_GRAPPLE;//FindItem("Grapple"); + item->use(ent,item); + ent->client->buttons &= ~BUTTON_ATTACK; + zc->routeindex++; +//gi.bprintf(PRINT_HIGH,"Groff 0!\n"); + } + } + if(k) + { + if(zc->routeindex < CurrentIndex) + { + if(Route[zc->routeindex].state == GRS_GRAPSHOT) + { + if(1/*TraceX(ent,Route[zc->routeindex + 1].Tcourner)*/) zc->routeindex++; + } + } + } + } +/* else if(ent->client->ctf_grapple) + { + if(ent->client->ctf_grapplestate == CTF_GRAPPLE_STATE_PULL + && Route[zc->routeindex - 1].state == GRS_GRAPRELEASE) + { + ent->client->buttons &= ~BUTTON_ATTACK; + CTFResetGrapple(ent->client->ctf_grapple); + } + }*/ + } +//>>PON-CTF +// if(trace_priority == TRP_ALLKEEP) goto VCHCANSEL; + + if(Route[zc->routeindex].state >= GRS_NORMAL) Search_NearlyPod(ent); + + Get_RouteOrigin(zc->routeindex,v); + + x = v[2] - ent->s.origin[2]; + + if(zc->zcstate & STS_WAITSMASK) zc->rt_locktime = level.time + FRAMETIME * POD_LOCKFRAME; + else if(Route[zc->routeindex].state <= GRS_ITEMS && (x > JumpMax && !zc->waterstate) + && !(zc->zcstate & STS_LADDERUP)) + { + if(zc->rt_locktime <= level.time) + { +#ifdef _DEBUG +gi.bprintf(PRINT_HIGH,"OFF 2\n"); //ppx +#endif + zc->route_trace = false; + zc->rt_releasetime = level.time + FRAMETIME * POD_RELEFRAME; + } + } + else if(!TraceX(ent,v) /*&& ent->client->ctf_grapple == NULL*/) + { + k = false; + if(ent->groundentity) + { + if(ent->groundentity->classname[0] == 'f') + { + if(/*!Q_stricmp(ent->groundentity->classname, "func_plat") + ||*/ !Q_stricmp(ent->groundentity->classname, "func_train")) + { + zc->rt_locktime = level.time + FRAMETIME * POD_LOCKFRAME; + k = true; + } + } + } +// if(Route[zc->routeindex].state == GRS_ONTRAIN +// && /*Route[zc->routeindex].ent->trainteam*/) zc->rt_locktime = level.time + FRAMETIME * POD_LOCKFRAME; +//PON + if(ent->client->ctf_grapple) + { + if(!VectorCompare(ent->s.origin,ent->s.old_origin)) zc->rt_locktime += FRAMETIME; + } +//PON + if(zc->rt_locktime <= level.time && !k) + { +// gi.bprintf(PRINT_HIGH,"shit1"); +#ifdef _DEBUG +gi.bprintf(PRINT_HIGH,"OFF 3\n"); //ppx +#endif + zc->route_trace = false; + zc->rt_releasetime = level.time + FRAMETIME * POD_RELEFRAME; + } + } + else + { + if(Route[zc->routeindex].state > GRS_ITEMS + && Route[zc->routeindex].state <= GRS_ONPLAT) + { + if(0/*Route[zc->routeindex].state == GRS_ONPLAT && Route[zc->routeindex].ent->moveinfo.state != PSTATE_BOTTOM*/) + { +#ifdef _DEBUG +gi.bprintf(PRINT_HIGH,"OFF 4\n"); //ppx +#endif + zc->route_trace = false; + zc->rt_releasetime = level.time + FRAMETIME * POD_RELEFRAME; + } + else if(0/*Route[zc->routeindex].ent->union_ent->solid != SOLID_TRIGGER*/) + { +#ifdef _DEBUG +gi.bprintf(PRINT_HIGH,"OFF 5\n"); //ppx +#endif + zc->route_trace = false; + zc->rt_releasetime = level.time + FRAMETIME * POD_RELEFRAME; + } + else zc->rt_locktime = level.time + FRAMETIME * POD_LOCKFRAME; + } + else zc->rt_locktime = level.time + FRAMETIME * POD_LOCKFRAME; + } + } + else + { + if(ent->client->ctf_grapple) + { + item = FindItem("Grapple"); + ent->client->pers.weapon = item; + ent->s.sound = 0; + ShowGun(ent); + ent->client->buttons &= ~BUTTON_ATTACK; + } + } + if(trace_priority == TRP_ALLKEEP) + { + if(ent->client->ctf_grapple) + { + rs_trace = gi.trace (ent->s.origin, ent->maxs, ent->mins, ent->s.origin,ent, MASK_BOTSOLIDX); + if(rs_trace.allsolid || rs_trace.startsolid || rs_trace.fraction != 1.0) + ent->client->ps.pmove.pm_flags |= PMF_DUCKED; + } + goto VCHCANSEL; + } + //-------------------------------------------------------------------------------------- + //search for items + if(!chedit->value && zc->second_target == NULL) + { + pickup_pri = false; //pickup priority off + k = false; + + zc->secondinterval++; + //when tracing routes + if(zc->route_trace && zc->secondinterval > 40) + { + for(i = zc->routeindex ; i < (zc->routeindex + 20); i++) + { + if(i >= CurrentIndex) break; + if(Route[i].state == GRS_ITEMS) + { + if(Route[i].ent->solid == SOLID_TRIGGER) + { + pickup_pri = true; + break; + } + } + } + } + + if(1/*!k*/) + { + if(zc->secondinterval > 40) + { + if(!(zc->zcstate & STS_WAITSMASK )) + { + Bot_SearchItems(ent); + if(zc->second_target != NULL && pickup_pri) + { + for(i = zc->routeindex ; i < (zc->routeindex + 20); i++) + { + if(i >= CurrentIndex) break; + if(Route[i].state == GRS_ITEMS) + { + if(Route[i].ent == zc->second_target) + { + zc->second_target = NULL; + break; + } + } + } + } + } + } + } + + if(zc->secondinterval > 40/*zc->second_target != NULL*/ /*&& !k*/) + { + zc->secondinterval = Bot[zc->botindex].param[BOP_PICKUP] * 4; + if(zc->secondinterval > 36) zc->secondinterval = 36; + if(zc->secondinterval < 0) zc->secondinterval = 0; + } + + if(ent->client->zc.objshot) goto VCHCANSEL; //object shot!! + } + + //-------------------------------------------------------------------------------------- + //go up ladder + // + // + // + // 梯子を登る + // + // + //-------------------------------------------------------------------------------------- + + if(zc->zcstate & STS_LADDERUP) + { +#ifdef _DEBUG +gi.bprintf(PRINT_HIGH,"ladder UP!! %f %i\n",zc->moveyaw,ent->waterlevel); +#endif + if(ent->waterlevel > 1) + { +/* if(zc->route_trace ) + { + Get_RouteOrigin(zc->routeindex,v); + Bot_Watermove ( ent,pos,dist,upd)*/ + ent->velocity[2] = VEL_BOT_WLADRUP; +// if(VectorCompare(ent->s.origin,ent->s.old_origin)) ent->velocity[2] += 50; + } + else + { + ent->velocity[2] = VEL_BOT_LADRUP; +// if(VectorCompare(ent->s.origin,ent->s.old_origin)) ent->velocity[2] += 50; + } + + VectorCopy(ent->mins,trmin); + trmin[2] += 20; + + yaw = zc->moveyaw * M_PI * 2 / 360; + touchmin[0] = cos(yaw) * 32;//96 ; + touchmin[1] = sin(yaw) * 32;//96 ; + touchmin[2] = 0; + + VectorAdd(ent->s.origin,touchmin,touchmax); + + rs_trace = gi.trace (ent->s.origin, trmin/*ent->mins*/,ent->maxs, touchmax,ent, MASK_BOTSOLID/*MASK_PLAYERSOLID*/); + + plane = rs_trace.plane; + + if(!(rs_trace.contents & CONTENTS_LADDER) && !rs_trace.allsolid /*&& !rs_trace.startsolid*/) + { +#ifdef _DEBUG +gi.bprintf(PRINT_HIGH,"ladder OFF1!!\ncont %x %x\nall %i\nstart %i\n",rs_trace.contents,i,rs_trace.allsolid,rs_trace.startsolid); +#endif + if(ent->velocity[2] <= VEL_BOT_LADRUP && !ent->waterlevel) ent->velocity[2] = VEL_BOT_LADRUP; + zc->zcstate &= ~STS_LADDERUP; + ent->moveinfo.speed = 0.25; + if(zc->route_trace) + { + Get_RouteOrigin(zc->routeindex,v); + if(VectorLength(v) > 32) + { + VectorSubtract(v,ent->s.origin,v); + zc->moveyaw = Get_yaw(v); + if(trace_priority < TRP_ANGLEKEEP) ent->s.angles[YAW] = zc->moveyaw; + } + else zc->routeindex++; + } + } + else + { +//pon + if(!rs_trace.allsolid) + { + VectorCopy(rs_trace.endpos,ent->s.origin); + } + +//pon +// ent->moveinfo.speed = 1.0; + VectorCopy(ent->s.origin,touchmin); + touchmin[2] += 8; + + rs_trace = gi.trace (ent->s.origin, ent->mins,ent->maxs, touchmin,ent, MASK_BOTSOLID/*MASK_PLAYERSOLID*/ ); + + x = rs_trace.endpos[2] - ent->s.origin[2]; + + if(ent->waterlevel ) + { + ent->s.origin[2] += x; + } + else + { + ent->s.origin[2] += x; + } + + e = rs_trace.ent; + + if(x == 0/*VectorCompare(ent->s.origin,ent->s.old_origin)*/) + { + x = Get_yaw(plane.normal); + + //right + VectorCopy(ent->s.origin,v); + yaw = x + 90; + if(yaw > 180) yaw -= 360; + yaw = yaw * M_PI * 2 / 360; + touchmin[0] = cos(yaw) * 48 ; + touchmin[1] = sin(yaw) * 48 ; + touchmin[2] = 0; + VectorAdd(ent->s.origin,touchmin,trmin); + + VectorCopy(trmin,trmax); + trmin[2] += 32; + trmax[2] += 64; + rs_trace = gi.trace(trmin,NULL,NULL,trmax,ent,MASK_BOTSOLID); + f1 = rs_trace.fraction; + + //left + VectorCopy(ent->s.origin,v); + iyaw = x -90 ; + if(iyaw < 180) iyaw += 360; + iyaw = iyaw * M_PI * 2 / 360; + touchmin[0] = cos(iyaw) * 48 ; + touchmin[1] = sin(iyaw) * 48 ; + touchmin[2] = 0; + VectorAdd(ent->s.origin,touchmin,trmin); + + VectorCopy(trmin,trmax); + trmin[2] += 32; + trmax[2] += 64; + rs_trace = gi.trace(trmin,NULL,NULL,trmax,ent,MASK_BOTSOLID); + f2 = rs_trace.fraction; + + x = 0.0; + if(f1 == 1.0 && f2 != 1.0) x = yaw; + else if(f1 != 1.0 && f2 == 1.0) x = iyaw; + + if(x != 0.0) + { + touchmin[0] = cos(x) * 4 ; + touchmin[1] = sin(x) * 4 ; + touchmin[2] = 0; + VectorAdd(ent->s.origin,touchmin,trmin); + rs_trace = gi.trace(ent->s.origin,ent->mins,ent->maxs,trmin,ent,MASK_BOTSOLID); + if(rs_trace.startsolid || rs_trace.allsolid) x = 0; + else VectorCopy(rs_trace.endpos,ent->s.origin); + } + + if(x == 0.0) + { +#ifdef _DEBUG +gi.bprintf(PRINT_HIGH,"ladder OFF2!!\n"); +#endif + k = 0; + if(e) + { + if(Q_stricmp (e->classname, "func_door") == 0) + { + if(e->moveinfo.state == PSTATE_UP) k = true; + } + } + if(!k) + { + zc->moveyaw += 180; + if(zc->moveyaw > 180) zc->moveyaw -= 360; + zc->zcstate &= ~STS_LADDERUP; + ent->moveinfo.speed = 0.25; + } + } + } + } + + if(zc->zcstate & STS_LADDERUP) + { + if(zc->route_trace ) + { + Get_RouteOrigin(zc->routeindex,v); + if(v[2] < ent->s.origin[2]) + { + VectorSubtract(ent->s.origin,v,vv); + vv[2] = 0; + if(VectorLength(vv) < 32) zc->routeindex++; + } + } + + + ent->velocity[0] = 0; + ent->velocity[1] = 0; + goto VCHCANSEL_L; + } + } + + //-------------------------------------------------------------------------------------- + //bot's true moving yaw,yaw pitch set + // j is used ground entity check section + // + // + // + // 移動方向決定 + // + // + //-------------------------------------------------------------------------------------- + + j = 0; + if(ent->groundentity && ent->waterlevel <= 1 && trace_priority < TRP_ANGLEKEEP) ent->s.angles[PITCH] = 0; + if(zc->second_target != NULL ) + { + if((zc->second_target->s.origin[2] - ent->s.origin[2]) > 32 && !ent->waterlevel) + { + x = zc->second_target->moveinfo.start_origin[2] - ent->s.origin[2]; + if(x <= 32 && x > -24 && Bot_trace2 (ent,zc->second_target->moveinfo.start_origin)) + { + VectorSubtract(zc->second_target->moveinfo.start_origin,ent->s.origin,temppos); + k = false; + yaw = temppos[2]; + temppos[2] = 0; + x = VectorLength(temppos); + + if(yaw < -32 && x < 32) k = true; + + if(!k) + { + if(!ent->groundentity && !ent->waterlevel) + { + if(trace_priority < TRP_ANGLEKEEP) ent->s.angles[YAW] = Get_yaw(temppos); + } + else if(ent->groundentity || ent->waterlevel ) + { + if(trace_priority < TRP_MOVEKEEP) zc->moveyaw = Get_yaw(temppos); + if(trace_priority < TRP_ANGLEKEEP) ent->s.angles[YAW] = zc->moveyaw; + } + + if(x < dist && fabs(temppos[2]) < 24) dist = x;//pon + } + j = -1; + } + } + else + { + VectorSubtract(zc->second_target->s.origin,ent->s.origin,temppos); + if(ent->waterlevel && !ent->groundentity && trace_priority < TRP_ANGLEKEEP) ent->s.angles[PITCH] = Get_pitch(temppos); + + if(!ent->groundentity && !ent->waterlevel && trace_priority < TRP_ANGLEKEEP) + { + temppos[2] = 0; + if(VectorLength(temppos) > 32) + { + if(trace_priority < TRP_ANGLEKEEP) ent->s.angles[YAW] = Get_yaw(temppos); + } + } + else if(ent->groundentity || ent->waterlevel ) + { + k = false; + yaw = temppos[2]; + temppos[2] = 0; + x = VectorLength(temppos); + + if(yaw < -32 && x < 32) k = true; + + if(!k) + { + if(trace_priority < TRP_MOVEKEEP) zc->moveyaw = Get_yaw(temppos); //set the movin' yaw + if(trace_priority < TRP_ANGLEKEEP) ent->s.angles[YAW] = zc->moveyaw; + if(x < dist && fabs(yaw) < JumpMax) dist = x; + } + } + j = -1; + } + } + else + { + if(ent->groundentity && !zc->route_trace) + { + if(trace_priority < TRP_MOVEKEEP) zc->moveyaw = ent->s.angles[YAW]; + } + else if(trace_priority < TRP_ANGLEKEEP) ent->s.angles[YAW] = zc->moveyaw ; + } + +/* if(zc->first_target != NULL) + { + VectorSubtract(zc->first_target->s.origin,ent->s.origin,temppos); + ent->s.angles[YAW] = Get_yaw(temppos); //set the model's yaw + ent->s.angles[PITCH] = Get_pitch(temppos); + } +*/ + //チームプレイ時のルーチン + if(ctf->value ||((int)(dmflags->value) & (DF_MODELTEAMS | DF_SKINTEAMS))) + { + if(ctf->value) + { + if(zc->ctfstate == CTFS_SUPPORTER) + { + if(zc->followmate) + { + if(zc->followmate->inuse) + if(zc->followmate->client->zc.ctfstate != CTFS_CARRIER) + { + zc->ctfstate = CTFS_OFFENCER; + zc->followmate = NULL; + } + } + + if(zc->second_target == NULL) j = 1; + } + else j = 0; + } + else + { + if(zc->tmplstate == TMS_FOLLOWER && zc->second_target == NULL) j = 1; + else j = 0; + } + + if(j/*zc->tmplstate == TMS_FOLLOWER && zc->second_target == NULL*/) + { + if(zc->followmate) + { + k = Bot_traceS(ent,zc->followmate); + if(k || zc->route_trace) zc->matelock = level.time + FRAMETIME * 5; + if(!zc->followmate->inuse || zc->followmate->deadflag || zc->matelock <= level.time) + { + if(ctf->value) zc->ctfstate = CTFS_OFFENCER; + else zc->tmplstate = TMS_NONE; + zc->followmate = NULL; + } + else + { + VectorSubtract(zc->followmate->s.origin,ent->s.origin,v); + if(VectorLength(v) < 200) + { + if(k && zc->followmate->client->zc.route_trace + && (zc->followmate->client->zc.routeindex - 2) > 0 + && (ent->svflags & SVF_MONSTER)) + { + zc->routeindex = zc->followmate->client->zc.routeindex - 2; + zc->route_trace = true; + if(zc->followmate->client->zc.havetarget) + { + zc->targetindex = zc->followmate->client->zc.targetindex; + } + } + else if(!(ent->svflags & SVF_MONSTER)) + { + zc->moveyaw = Get_yaw(v); + //if(VectorLength(v) < 100) trace_priority = TRP_ALLKEEP; + //else trace_priority = TRP_MOVEKEEP; + } + if(VectorLength(v) < 100) + { + if(!v[0]) v[0] = 1; + if(!v[1]) v[1] = 1; + v[0] *= -1; + v[1] *= -1; + if(trace_priority < TRP_MOVEKEEP) zc->moveyaw = Get_yaw(v); + if(trace_priority < TRP_ANGLEKEEP) + { + ent->s.angles[YAW] = zc->moveyaw; + ent->s.angles[PITCH] = Get_pitch(v); + } + } + else if(trace_priority < TRP_MOVEKEEP) + { + goto VCHCANSEL; + } + } + else if(ent->groundentity || ent->waterlevel ) + { + if(zc->followmate->client->zc.route_trace && (ent->svflags & SVF_MONSTER)) zc->routeindex = zc->followmate->client->zc.routeindex; + else + { + if(trace_priority < TRP_MOVEKEEP) zc->moveyaw = Get_yaw(v); + if(trace_priority < TRP_ANGLEKEEP) + { + ent->s.angles[YAW] = zc->moveyaw; + ent->s.angles[PITCH] = Get_pitch(v); + } + } + } + } + } + else + { + if(ctf->value) zc->ctfstate = CTFS_OFFENCER; + else zc->tmplstate = TMS_NONE; + } + } + } + + //ctf route index fix + if(ctf->value && !chedit->value) + { + j = 0; + if(ent->client->resp.ctf_team == CTF_TEAM1) + { +//if(zc->ctfstate == CTFS_CARRIER) +// gi.bprintf(PRINT_HIGH,"I am carrierX!!\n"); + if(zc->ctfstate == CTFS_DEFENDER + || zc->ctfstate == CTFS_CARRIER + || zc->ctfstate == CTFS_SUPPORTER) j = FOR_FLAG1; + else j = FOR_FLAG2; + } + else if(ent->client->resp.ctf_team == CTF_TEAM2) + { + if(zc->ctfstate == CTFS_DEFENDER + || zc->ctfstate == CTFS_CARRIER + || zc->ctfstate == CTFS_SUPPORTER) j = FOR_FLAG2; + else j = FOR_FLAG1; + } + + if(zc->route_trace) + { + if(Route[zc->routeindex].state < GRS_ITEMS + && Route[zc->routeindex].linkpod[MAXLINKPOD - 1]) + { + k = Route[zc->routeindex].linkpod[MAXLINKPOD - 1]; + if(j == FOR_FLAG1) + { + if(k & CTF_FLAG2_FLAG) + { +//gi.bprintf(PRINT_HIGH,"Wrong way 1\n"); + for(i = 0;i < (MAXLINKPOD - 1);i++) + { + if(!Route[zc->routeindex].linkpod[i]) break; + k = Route[Route[zc->routeindex].linkpod[i]].linkpod[MAXLINKPOD - 1]; + if(!(k & CTF_FLAG2_FLAG)) + { + zc->routeindex = Route[zc->routeindex].linkpod[i];// zc->route_trace = false; + zc->havetarget = false; +//gi.bprintf(PRINT_HIGH,"fixed for flag 1\n"); + } + } + } + else if(!zc->havetarget && zc->ctfstate == CTFS_CARRIER) + { + zc->havetarget = true; + zc->targetindex = Route[zc->routeindex].linkpod[MAXLINKPOD - 1] & 0x7FFF; + } + } + else if(j == FOR_FLAG2) + { + if(!(k & CTF_FLAG2_FLAG)) + { +//gi.bprintf(PRINT_HIGH,"Wrong way 2\n"); + for(i = 0;i < (MAXLINKPOD - 1);i++) + { + if(!Route[zc->routeindex].linkpod[i]) break; + k = Route[Route[zc->routeindex].linkpod[i]].linkpod[MAXLINKPOD - 1]; + if(k & CTF_FLAG2_FLAG) + { + zc->routeindex = Route[zc->routeindex].linkpod[i];// zc->route_trace = false; + zc->havetarget = false; +//gi.bprintf(PRINT_HIGH,"fixed for flag 2\n"); + } + } + } + else if(!zc->havetarget && zc->ctfstate == CTFS_CARRIER) + { + zc->havetarget = true; + zc->targetindex = Route[zc->routeindex].linkpod[MAXLINKPOD - 1] & 0x7FFF; + } + } + } + } + } + + if(1/*!(zc->zcstate & STS_WAITSMASK)*/) + { + //ルートトレース用index検索 + if(!zc->route_trace && zc->rt_releasetime <= level.time) + { + //zc->routeindex; + if(zc->routeindex >= CurrentIndex) zc->routeindex = 0; + //fix route index + for(i = 0;i < CurrentIndex && i < MAX_SEARCH;i++) + { + if(Route[zc->routeindex].state == GRS_GRAPHOOK) + { + while(1) + { + ++zc->routeindex; + if(zc->routeindex >= CurrentIndex){i = CurrentIndex; break;} + if(Route[zc->routeindex].state == GRS_GRAPRELEASE) {++zc->routeindex; break;} + } + continue; + } + else if(Route[zc->routeindex].state == GRS_GRAPRELEASE) {++zc->routeindex; continue;} + else if(ctf->value && !chedit->value) + { + if(Route[zc->routeindex].state < GRS_ITEMS + && Route[zc->routeindex].linkpod[MAXLINKPOD - 1]) + { + k = Route[zc->routeindex].linkpod[MAXLINKPOD - 1]; + if(j == FOR_FLAG1) + { + if(k & CTF_FLAG2_FLAG) + { + zc->routeindex = (k & 0x7FFF); +//gi.bprintf(PRINT_HIGH,"skipped to flag 1 %x\n",k); + } + } + else if(j == FOR_FLAG2) + { + if(!(k & CTF_FLAG2_FLAG)) + { + zc->routeindex = (k & 0x7FFF); +//gi.bprintf(PRINT_HIGH,"skipped to flag 2 %x\n",k); + } + } + } + } + Get_RouteOrigin(zc->routeindex,v); +// VectorSubtract(Route[k].Pt,ent->s.origin,temppos); + if(Route[zc->routeindex].state <= GRS_ITEMS && TraceX(ent,v)) + { + if(fabs(v[2] - ent->s.origin[2]) <= JumpMax || zc->waterstate == WAS_IN) + { + zc->route_trace = true; + zc->rt_locktime = level.time + FRAMETIME * POD_LOCKFRAME; + break; + } + } + if(++zc->routeindex >= CurrentIndex) zc->routeindex = 0; + } + } + else if(zc->route_trace) + { + if(Route[zc->routeindex].state == GRS_ONDOOR) + { + if(1/*!Route[zc->routeindex].ent->union_ent*/) + { + it_ent = Route[zc->routeindex].ent; + if(zc->routeindex + 1 < CurrentIndex ) + { + Get_RouteOrigin(zc->routeindex + 1,v); + zc->route_trace = false; + j = TraceX(ent,v); + zc->route_trace = true; + if((!j || (v[2] - ent->s.origin[2]) > JumpMax )&& it_ent->union_ent) + { + + k = false; + if((it_ent->union_ent->s.origin[2] - ent->s.origin[2]) > JumpMax) k = true; + + VectorSubtract(it_ent->union_ent->s.origin,ent->s.origin,temppos); + yaw = Get_yaw(temppos); + if(trace_priority < TRP_ANGLEKEEP) + { + ent->s.angles[PITCH] = Get_pitch(temppos); + ent->s.angles[YAW] = yaw; + } + temppos[2] = 0; + x = VectorLength(temppos); + + if( x == 0/*< dist*/ || k) + { + if(it_ent->nextthink >= level.time) zc->rt_locktime = level.time + FRAMETIME * POD_LOCKFRAME; + goto VCHCANSEL; //if center position move cancel + } + if(x < dist) dist = x; + if(it_ent->nextthink > level.time) zc->rt_locktime = it_ent->nextthink + FRAMETIME * POD_LOCKFRAME; + else zc->rt_locktime = level.time + FRAMETIME * POD_LOCKFRAME; + if(trace_priority < TRP_MOVEKEEP) zc->moveyaw = yaw; + goto GOMOVE; + } + } + + } + zc->routeindex++; + } + + if(zc->routeindex < CurrentIndex) + { + Get_RouteOrigin(zc->routeindex,v); +/* if(Route[zc->routeindex].state == GRS_ITEMS) + { + if(Route[zc->routeindex].ent->solid == SOLID + }*/ + + k = false; + if(Route[zc->routeindex].state == GRS_PUSHBUTTON) + { + it_ent = Route[zc->routeindex].ent; + if(it_ent->health && (it_ent->takedamage || it_ent->moveinfo.state != PSTATE_TOP)) + { + k = 2; + } + else if(it_ent->health) + { + zc->routeindex++; + if(zc->routeindex < CurrentIndex) Get_RouteOrigin(zc->routeindex,v); + } + } + else + { + //VectorCopy(ent->mins,touchmin); + //touchmin[2] += 20; + VectorSet(touchmax,16,16,4); + VectorSet(touchmin,-16,-16,0); + rs_trace = gi.trace(ent->s.origin,touchmin,touchmax,v,ent,MASK_SHOT); + if(rs_trace.fraction != 1.0 && rs_trace.ent) + { + if((rs_trace.ent->health || rs_trace.ent->takedamage) + && rs_trace.ent->classname[0] != 'p' + && rs_trace.ent->classname[0] != 'b') + { +//gi.bprintf(PRINT_HIGH,"shushu!\n"); + zc->rt_locktime = level.time + FRAMETIME * POD_LOCKFRAME; + it_ent = rs_trace.ent; + k = true; + } + } + } + + //トリガを撃つ + if(k && !(ent->client->buttons & BUTTON_ATTACK)) + { +//gi.bprintf(PRINT_HIGH,"ooooooo!\n"); + trmin[0] = (it_ent->absmin[0] + it_ent->absmax[0])/2; + trmin[1] = (it_ent->absmin[1] + it_ent->absmax[1])/2; + trmin[2] = (it_ent->absmin[2] + it_ent->absmax[2])/2; + + //if button + if(k == 2) + { + VectorSet(touchmin, 0, 0, ent->viewheight-8); + VectorAdd(ent->s.origin,touchmin,touchmin); + + rs_trace = gi.trace(it_ent->union_ent->s.origin,NULL,NULL,trmin,it_ent->union_ent,MASK_SHOT); + VectorSubtract(rs_trace.endpos,ent->s.origin,trmax); + } + else VectorSubtract(v,ent->s.origin,trmax); + +//gi.bprintf(PRINT_HIGH,"shoot!\n"); + //爆発モノの時は持ち替え + i = Get_KindWeapon(ent->client->pers.weapon); + if(!zc->first_target && it_ent->takedamage) + { + if(i == WEAP_GRENADES + || i == WEAP_GRENADELAUNCHER + || i == WEAP_ROCKETLAUNCHER + || i == WEAP_PHALANX + || i == WEAP_BFG) + { + item = Fdi_BLASTER;//FindItem("Blaster"); + item->use(ent,item); +//if(ent->client->newweapon) gi.bprintf(PRINT_HIGH,"selected %s\n",ent->client->newweapon->pickup_name); + } + } + if(!zc->first_target || it_ent->takedamage) + { + ent->s.angles[YAW] = Get_yaw(trmax); + ent->s.angles[PITCH] = Get_pitch(trmax); + } + if(it_ent->takedamage) ent->client->buttons |= BUTTON_ATTACK; + if(k == 2) + { + if(it_ent->moveinfo.state != PSTATE_TOP) goto VCHCANSEL; + } + else + { + if(!TraceX(ent,v)) goto VCHCANSEL; + } + //if(j)goto VCHCANSEL; + } + + if(Route[zc->routeindex].state == GRS_ONTRAIN && !zc->waterstate /*< WAS_IN*/ + /*ent->groundentity*/) + { + Get_RouteOrigin(zc->routeindex -1 ,trmin); + if((trmin[2] - ent->s.origin[2]) > /*2*/JumpMax + && (v[2] - ent->s.origin[2]) > JumpMax + && ent->waterlevel < 3) + { + zc->route_trace = false; +#ifdef _DEBUG +gi.bprintf(PRINT_HIGH,"OFF 10\n"); +#endif + } +/* if(!Q_stricmp(ent->groundentity->classname, "func_train")) + { + VectorCopy(ent->groundentity->union_ent->s.origin,v); + if(VectorLength(v) < 16 && (zc->routeindex + 1) < CurrentIndex) + { + zc->routeindex++; + Get_RouteOrigin(zc->routeindex,v); + } + + }*/ + + } + if(zc->waterstate == WAS_IN) f2 = 20; + else if(ent->groundentity) f2 = -8; + else f2 = 0; + if(ent->client->ps.pmove.pm_flags & PMF_DUCKED) f1 = -16; + else + { + if(zc->waterstate == WAS_IN) f1 = 24; + else if(/*zc->waterstate == WAS_FLOAT*/ent->waterlevel && ent->waterlevel < 3) + { + if(v[0] == ent->s.origin[0] && v[1] == ent->s.origin[1]) f1 = -300; + else f1 = -(JumpMax + 64); + } + else f1 = -(JumpMax + 64); + } +//到達チェック + if( Route[zc->routeindex].state == GRS_ONROTATE) yaw = -48; + else yaw = 12; + if(v[0] <= (ent->absmax[0] - yaw) && v[0] >= (ent->absmin[0] + yaw)) + { + if(v[1] <= (ent->absmax[1] - yaw) && v[1] >= (ent->absmin[1] + yaw)) + { + if((v[2] <= (ent->absmax[2] - f1) && v[2] >= (ent->absmin[2] + f2)) + || Route[zc->routeindex].state == GRS_ONROTATE + /*|| zc->waterstate == WAS_FLOAT*/) + { + if(zc->routeindex < CurrentIndex /*&& TraceX(ent,Route[zc->routeindex + 1].Pt)*/) + { +//アイテムリンクチェック1>> + if(Route[zc->routeindex].state <= GRS_ITEMS) + { + if(zc->havetarget) + { + for(i = 0;i < (MAXLINKPOD - (ctf->value != 0));i++) + { + k = Route[zc->routeindex].linkpod[i]; + if(!k) break; +//gi.bprintf(PRINT_HIGH,"tryto change index1\n"); + if(k > zc->routeindex && k < zc->targetindex) + { +//gi.bprintf(PRINT_HIGH,"change index1\n"); + if(1/*!ctf->value*/) + { + zc->routeindex = k; + break; + } + } + } + } + else if(random() < 0.2 && !ctf->value) + { + for(i = 0;i < (MAXLINKPOD - (ctf->value != 0));i++) + { + k = Route[zc->routeindex].linkpod[i]; + if(!k) break; + if(k > zc->routeindex && k < zc->targetindex) + { + if(random() < 0.5) + { + zc->routeindex = k; + break; + } + } + } + } + } +//アイテムリンクチェック<< + zc->routeindex++; + //not a normal pod + if(zc->routeindex < CurrentIndex) + { + if(Route[zc->routeindex].state != GRS_NORMAL && Route[zc->routeindex].ent) + { + //when items + if(0/*Route[zc->routeindex].state == GRS_ITEMS*/) + { + zc->second_target = Route[zc->routeindex].ent; + //zc->routeindex++; + } + } + } + else zc->routeindex = 0; + } + } + } + } + + if(zc->routeindex < CurrentIndex + && trace_priority && zc->second_target == NULL) + { + if(1/*TraceX(ent,Route[zc->routeindex].Pt)*/) + { + Get_RouteOrigin(zc->routeindex,v); + + VectorSubtract(v,ent->s.origin,temppos); + if(trace_priority < TRP_ANGLEKEEP) ent->s.angles[PITCH] = Get_pitch(temppos); + + k = false; +// if(zc->waterstate != WAS_IN && temppos[2] < 32) k = true; +// else if(zc->waterstate == WAS_IN) k = true; + + if(ent->groundentity /*|| ent->waterlevel ) && + /*temppos[2] < 32 || zc->waterstate != WAS_IN)*/ || ent->waterlevel/*zc->waterstate*/ ) + { + k = false; + yaw = temppos[2]; + temppos[2] = 0; + x = VectorLength(temppos); +// if(yaw > JumpMax) k = true; + + if(!k) + { + k = false; + if(trace_priority < TRP_MOVEKEEP) zc->moveyaw = Get_yaw(temppos); //set the movin' yaw + if((ent->groundentity || ent->waterlevel) && trace_priority < TRP_ANGLEKEEP) {ent->s.angles[YAW] = zc->moveyaw; k = true;} + + if(x < dist && fabs(yaw) < 20/*JumpMax*/&& k) + { + iyaw = Get_yaw(temppos); + i = Bot_moveT (ent,iyaw,temppos,x,&bottom); + rs_trace = gi.trace(/*ent->s.origin*/v,ent->mins,ent->maxs,v,ent,MASK_BOTSOLIDX); + + if(Route[zc->routeindex].state == GRS_ITEMS && !i) + { + if(x < 30) zc->routeindex++; + } + + else if((Route[zc->routeindex].state == GRS_ITEMS + || Route[zc->routeindex].state == GRS_NORMAL) + && !rs_trace.allsolid && !rs_trace.startsolid + && HazardCheck(ent,v) + && fabs(bottom) < 20 && i && !ent->waterlevel/*&& rs_trace.fraction == 1.0*/) + { + j = false; + if(v[2] < ent->s.origin[2] && bottom < 0) j = true; + else if(v[2] >= ent->s.origin[2] && bottom >= 0) j = true; + if(j) + { + VectorCopy(temppos,ent->s.origin); + VectorCopy(v,trmin); + dist -= x; +//アイテムリンクチェック2>> + if(Route[zc->routeindex].state <= GRS_ITEMS) + { + if(zc->havetarget) + { + for(i = 0;i < (MAXLINKPOD - (ctf->value != 0));i++) + { + j = Route[zc->routeindex].linkpod[i]; + if(!j) break; +//gi.bprintf(PRINT_HIGH,"tryto change index2\n"); + if(j > zc->routeindex && j < zc->targetindex) + { +//gi.bprintf(PRINT_HIGH,"change index2\n"); + zc->routeindex = j; + break; + } + } + } + } +//アイテムリンクチェック<< + zc->routeindex++; + if(i == 2) ent->client->ps.pmove.pm_flags |= PMF_DUCKED; + + Get_RouteOrigin(zc->routeindex,v); + VectorSubtract(v,ent->s.origin,temppos); + if(trace_priority < TRP_ANGLEKEEP) ent->s.angles[PITCH] = Get_pitch(temppos); + if(trace_priority < TRP_MOVEKEEP) zc->moveyaw = Get_yaw(temppos); + if(k && trace_priority < TRP_ANGLEKEEP) ent->s.angles[YAW] = zc->moveyaw; + } + } + else if((Route[zc->routeindex].state == GRS_ITEMS + || Route[zc->routeindex].state == GRS_NORMAL) + && fabs(bottom) < 20 && ent->waterlevel + /*&& !(zc->zcstate & STS_LADDERUP)*/) + { + j = false; + if(v[2] < ent->s.origin[2] && bottom < 0) j = true; + else if(v[2] >= ent->s.origin[2] && bottom >= 0) j = true; + if(j) + { + VectorCopy(temppos,ent->s.origin); + //VectorCopy(v,ent->s.origin); + VectorCopy(v,trmin); + dist -= x; + zc->routeindex++; + Get_RouteOrigin(zc->routeindex,v); + VectorSubtract(v,ent->s.origin,temppos); + if(trace_priority < TRP_ANGLEKEEP) ent->s.angles[PITCH] = Get_pitch(temppos); + if(trace_priority < TRP_MOVEKEEP) zc->moveyaw = Get_yaw(temppos); + if(k && trace_priority < TRP_ANGLEKEEP) ent->s.angles[YAW] = zc->moveyaw; + } + else dist = x; + } + else dist = x; + } + else if(x < dist) dist = x; + } + + k = false; + if((zc->routeindex - 1) >= 0 && + (Route[zc->routeindex].state == GRS_ONPLAT + || Route[zc->routeindex].state == GRS_ONTRAIN)) + { + Get_RouteOrigin(zc->routeindex - 1,v); + if(fabs(v[2] - ent->s.origin[2]) <= JumpMax) + { + if(zc->waterstate < WAS_IN + /*&& Route[zc->routeindex].ent->trainteam == NULL*/ + && Route[zc->routeindex].ent->nextthink > level.time) k = true; + } + + } + if(k && !(zc->zcstate & STS_WAITS)) + { + if((zc->routeindex + 1) < CurrentIndex) + { + Get_RouteOrigin(zc->routeindex + 1,v); + if((v[2] - ent->s.origin[2]) > JumpMax) + { + if((Route[zc->routeindex].ent->union_ent->s.origin[2] + - ent->s.origin[2]) > JumpMax + /*|| !TraceX(ent,v)*/) + { + zc->waitin_obj = Route[zc->routeindex].ent; + zc->zcstate |= STS_W_COMEPLAT; + k = false; + for(i = 1;i <=3;i++) + { + if(zc->routeindex - i >= 0) + { + Get_RouteOrigin(zc->routeindex - i,v); + if(zc->waitin_obj->absmax[0] < (v[0] + ent->mins[0])) k = true; + else if(zc->waitin_obj->absmax[1] < (v[1] + ent->mins[1])) k = true; + else if(zc->waitin_obj->absmin[0] > (v[0] + ent->maxs[0])) k = true; + else if(zc->waitin_obj->absmin[1] > (v[1] + ent->maxs[1])) k = true; + if(k) break; + } + } + if(k) VectorCopy(v,zc->movtarget_pt); + else Get_RouteOrigin(zc->routeindex - 1,zc->movtarget_pt); + + goto VCHCANSEL; + } + } + } + } + + } + //ent->s.angles[YAW] = zc->moveyaw; + } + } + else if(zc->routeindex >= CurrentIndex) + { +#ifdef _DEBUG +gi.bprintf(PRINT_HIGH,"OFF 6\n"); //ppx +#endif + zc->routeindex = 0; + zc->route_trace = false; + } + } + else + { +#ifdef _DEBUG +gi.bprintf(PRINT_HIGH,"OFF 7\n"); //ppx +#endif + zc->routeindex = 0; + zc->route_trace = false; + } + } + } + else + { +#ifdef _DEBUG +gi.bprintf(PRINT_HIGH,"OFF 8\n"); //ppx +#endif + zc->route_trace = false; + } + + //-------------------------------------------------------------------------------------- + //ground entity check + // + // + // + // あしもと確認 + // + // + //-------------------------------------------------------------------------------------- + + if(!(zc->zcstate & STS_W_DOOROPEN ) && (!ent->groundentity || ent->groundentity != zc->waitin_obj)) + { + k = false; + if(zc->waitin_obj) if(Q_stricmp (zc->waitin_obj->classname,"func_door")) k = true; + + if(!k) + { + zc->zcstate &= ~STS_WAITS; + zc->waitin_obj = NULL; + } + } + + if(ent->groundentity && /*!j &&*/ !(zc->zcstate & STS_WAITS) ) + { + it_ent = ent->groundentity; + if( it_ent->classname[0] == 'f') + { + if(Q_stricmp (it_ent->classname, "func_plat") == 0) + { +/*if(it_ent->moveinfo.state == PSTATE_UP && it_ent->nextthink <= level.time ) +gi.bprintf(PRINT_HIGH,"aw shit!!\n");*/ + if(it_ent->pos1[2] > it_ent->pos2[2] + && ((it_ent->moveinfo.state == PSTATE_UP && it_ent->velocity[2] > 0 ) || it_ent->moveinfo.state == PSTATE_BOTTOM) + /*&& it_ent->s.origin[2] != it_ent->s.old_origin[2]*/) + { +//gi.bprintf(PRINT_HIGH,"osssre onplat!!\n"); + zc->waitin_obj = it_ent; + zc->zcstate |= STS_W_ONPLAT; + if(zc->route_trace) + { +//gi.bprintf(PRINT_HIGH,"ore onplat!!\n"); + if(Route[zc->routeindex].ent == zc->waitin_obj + && Route[zc->routeindex].state == GRS_ONPLAT) + { +//gi.bprintf(PRINT_HIGH,"YEAH onplat!!\n"); + if(zc->waitin_obj->union_ent->s.origin[2] > (ent->s.origin[2] + 32)) + { + zc->zcstate &= ~STS_W_ONPLAT; + zc->zcstate |= STS_W_COMEPLAT; + } + else zc->routeindex++; + } + /* for(i = 0;i < 10;i++) + { + if((zc->routeindex + i) >= CurrentIndex) break; + if(!Route[zc->routeindex + i].index) break; + if(Route[zc->routeindex + i].state == GRS_ONPLAT + && Route[zc->routeindex + i].ent == zc->waitin_obj) + { + zc->routeindex += i + 1; + break; + } + }*/ + } + } + } + //on train + else if(Q_stricmp (it_ent->classname, "func_train") == 0 + && it_ent->nextthink >= level.time + && ((it_ent->s.origin[2] - it_ent->s.old_origin[2]) > 0 + || zc->route_trace)) +// && abs(it_ent->moveinfo.start_origin[2] - it_ent->moveinfo.end_origin[2]) > 54) + { +//gi.bprintf(PRINT_HIGH,"challenge!!\n"); + //route trace on + if(zc->route_trace && zc->routeindex > 0) + { + j = false; + k = zc->routeindex - 1; + for(i = 0;i < 3;i++) + { + if((k + i) < CurrentIndex) + { + if(Route[k + i].state == GRS_ONTRAIN) + { + if(Route[k + i].ent == it_ent) j = true; + else if(it_ent->trainteam != NULL) + { + e = it_ent->trainteam; + while(1) + { + if(e == it_ent) + { + break; + } + if(e == Route[k + i].ent) + { + j = true; + it_ent = e; + Route[k + i].ent = e; + break; + } + e = e->trainteam; + } + } + else if(/*e*/it_ent->target_ent) + { + if(VectorCompare(Route[k + i].Tcourner,/*e*/it_ent->target_ent->s.origin)) + { + j = true; + //it_ent = e; + break; + } + } + if(j) break; + } + } + else break; + } + if(j) + { +//gi.bprintf(PRINT_HIGH,"On train1!!\n"); + zc->zcstate |= STS_W_ONTRAIN; + zc->waitin_obj = it_ent; + zc->routeindex = k + i + 1; + } + } +/* if(Route[zc->routeindex - 1].state == GRS_ONTRAIN +// && it_ent->trainteam == NULL + && it_ent == Route[zc->routeindex - 1].ent) + { + zc->zcstate |= STS_W_ONTRAIN; + zc->waitin_obj = it_ent; + } + else if(Route[zc->routeindex].state == GRS_ONTRAIN + && it_ent == Route[zc->routeindex].ent + && zc->routeindex + 1 < CurrentIndex) + { + Get_RouteOrigin(zc->routeindex + 1,v); + if(!TraceX(ent,v)) + { + zc->zcstate |= STS_W_ONTRAIN; + zc->waitin_obj = it_ent; + } + zc->routeindex++; + } + else if(Route[zc->routeindex].state == GRS_ONTRAIN + && it_ent->trainteam + && zc->routeindex + 1 < CurrentIndex) + { + Get_RouteOrigin(zc->routeindex + 1,v); + if(!TraceX(ent,v)) + { + k = false; + e = it_ent->trainteam; + while(1) + { + if(e == it_ent) break; + if(e == Route[zc->routeindex].ent) + { + k = true; + break; + } + e = e->trainteam; + } + if(k) + { + zc->zcstate |= STS_W_ONTRAIN; + zc->waitin_obj = it_ent; + zc->routeindex++; + } + } + } + }*/ + else + { +// if(it_ent->moveinfo.start_origin[2] > it_ent->moveinfo.end_origin[2]) x = it_ent->moveinfo.end_origin[2]; +// else x = it_ent->moveinfo.start_origin[2]; + + if((it_ent->s.origin[2] - it_ent->s.old_origin[2]) > 0 + /*|| (it_ent->s.origin[2] == it_ent->s.old_origin[2] && x == it_ent->s.origin[2])*/ ) + { +//gi.bprintf(PRINT_HIGH,"On train2!!\n"); + zc->zcstate |= STS_W_ONTRAIN; + zc->waitin_obj = it_ent; + } + else if((it_ent->s.origin[2] - it_ent->s.old_origin[2]) > -2 + && trace_priority && zc->second_target == NULL) + { +//gi.bprintf(PRINT_HIGH,"On train3!!\n"); + zc->zcstate |= STS_W_ONTRAIN; + zc->waitin_obj = it_ent; + } + else zc->zcstate |= STS_W_DONT; + } + } + } + } + + if((zc->zcstate & STS_W_DONT) && ent->groundentity) + { + if(zc->zcstate & STS_W_ONPLAT) + { + if(Q_stricmp (ent->groundentity->classname, "func_plat")) + { + zc->zcstate &= ~STS_WAITS; + zc->waitin_obj = NULL; + } + } + else if(zc->zcstate & STS_W_ONTRAIN) + { + if(Q_stricmp (ent->groundentity->classname, "func_train")) + { + zc->zcstate &= ~STS_WAITS; + zc->waitin_obj = NULL; + } + } + else if(zc->zcstate & (STS_W_ONDOORUP | STS_W_ONDOORDWN)) + { + if(Q_stricmp (ent->groundentity->classname, "func_door")) + { + zc->zcstate &= ~STS_WAITS; + zc->waitin_obj = NULL; + } + } + else + { + zc->zcstate &= ~STS_WAITS; + zc->waitin_obj = NULL; + } + } + + + //on plat + else if(( + (zc->zcstate & STS_W_ONPLAT) + || (zc->zcstate & STS_W_COMEPLAT) + || (zc->zcstate & STS_W_ONDOORUP) + || (zc->zcstate & STS_W_ONDOORDWN)) && !(zc->zcstate & STS_W_DONT)) + { + k = false; + //if door + if(zc->zcstate & (STS_W_ONDOORUP | STS_W_ONDOORDWN) ) + { + // up + if(zc->zcstate & STS_W_ONDOORUP) + { + if(zc->waitin_obj->moveinfo.state == PSTATE_UP + || zc->waitin_obj->moveinfo.state == PSTATE_BOTTOM) k = true; + } + // down + else + { + if(zc->waitin_obj->moveinfo.state == PSTATE_TOP + || zc->waitin_obj->moveinfo.state == PSTATE_DOWN) k = true; + } + } + else if(zc->zcstate & STS_W_COMEPLAT) + { + if(Route[zc->routeindex].state == GRS_ONTRAIN) + { + if(!TraceX(ent,/*zc->waitin_obj*/Route[zc->routeindex].ent->union_ent->s.origin)) k = true; + if((/*zc->waitin_obj*/Route[zc->routeindex].ent->union_ent->s.origin[2] + + 8 - ent->s.origin[2]) > JumpMax) k = true; + } + else + { + if((zc->waitin_obj->union_ent->s.origin[2] + - ent->s.origin[2]) > JumpMax) k = true; + } +// if(zc->waitin_obj->velocity[2] == 0) k = false; + if(zc->routeindex - 1 > 0 && zc->waterstate < WAS_IN) + { + Get_RouteOrigin(zc->routeindex -1 ,trmin); + if((trmin[2] - ent->s.origin[2]) > JumpMax + && (v[2] - ent->s.origin[2]) > JumpMax) +// Get_RouteOrigin(zc->routeindex - 1,v); +// if((v[2] - ent->s.origin[2]) > JumpMax) + k = false; + } + } + else + { + if(/*!k &&*/ zc->waitin_obj->moveinfo.state == PSTATE_UP + || zc->waitin_obj->moveinfo.state == PSTATE_BOTTOM) k = true; + + if(zc->waitin_obj->moveinfo.state == PSTATE_BOTTOM) plat_go_up (zc->waitin_obj); + + if(zc->route_trace) + { + Get_RouteOrigin(zc->routeindex,v); + if(ent->s.origin[2] > v[2] ) k = 2; + } + } + //have target + if(/*j ||*/ k != true) + { + if(k == 2) zc->zcstate |= STS_W_DONT; + else + { + zc->zcstate &= ~STS_WAITS; + zc->waitin_obj = NULL; + } + } + else + { + if(zc->zcstate & STS_W_COMEPLAT) + { + k = false; + if(zc->routeindex -1 >0) + { + if(1/*Route[zc->routeindex - 1].state <= GRS_ITEMS*/) + { + //Get_RouteOrigin(zc->routeindex - 1,trmax); + VectorCopy(zc->movtarget_pt,trmax); + trmax[2] = 0; + k = true; + } + } + if(!k) goto VCHCANSEL; + } + else + { + trmax[0] = (zc->waitin_obj->absmin[0] + zc->waitin_obj->absmax[0]) / 2; + trmax[1] = (zc->waitin_obj->absmin[1] + zc->waitin_obj->absmax[1]) / 2; + trmax[2] = 0; + } + VectorSubtract(trmax,ent->s.origin,temppos); + yaw = temppos[2]; + temppos[2] = 0; + x = VectorLength(temppos); + if( x == 0) goto VCHCANSEL; //if center position move cancel + if( x < dist) dist = x; + + if(trace_priority < TRP_MOVEKEEP) zc->moveyaw = Get_yaw(temppos); + } + } + // on train + else if(zc->zcstate & STS_W_ONTRAIN) + { + i = false; + + if(zc->route_trace) + { + Get_RouteOrigin(zc->routeindex,v); + + if((zc->routeindex - 1) >= 0) + { + if(Route[zc->routeindex - 1].state != GRS_ONTRAIN) i = true; + } + else i = true; + + if(TraceX(ent,v)) + { + x = v[2] - /*zc->waitin_obj->union_ent->s.origin[2];*/ent->s.origin[2]; + if(x <= JumpMax) + { +//gi.bprintf(PRINT_HIGH,"released!!! %f %i\n",x,Route[zc->routeindex].state); + i = true; + } + else zc->rt_locktime = level.time + FRAMETIME * POD_LOCKFRAME; + } + else zc->rt_locktime = level.time + FRAMETIME * POD_LOCKFRAME; + } + else if(j || (zc->waitin_obj->s.origin[2] - zc->waitin_obj->s.old_origin[2]) <= 0 ) i = true; + + if(i) + { + zc->zcstate |= STS_W_DONT; + zc->zcstate &= ~STS_WAITS; +// zc->waitin_obj = NULL; + } + else + { + k = false; + if(zc->route_trace) + { + rs_trace = gi.trace(ent->s.origin,NULL,NULL,v,ent,MASK_BOTSOLIDX/*MASK_PLAYERSOLID*/); + if(rs_trace.ent == zc->waitin_obj ) + { + rs_trace = gi.trace(v,NULL,NULL,ent->s.origin,ent,MASK_BOTSOLIDX/*MASK_PLAYERSOLID*/); + if(rs_trace.ent == zc->waitin_obj ) + { + VectorSubtract(v,ent->s.origin,temppos); + k = true; + } + } + } + if(!k) + { +//gi.bprintf(PRINT_HIGH,"ponko1!\n"); + VectorCopy(zc->waitin_obj->union_ent->s.origin,trmax); + //trmax[0] = (zc->waitin_obj->absmin[0] + zc->waitin_obj->absmax[0]) / 2; + //trmax[1] = (zc->waitin_obj->absmin[1] + zc->waitin_obj->absmax[1]) / 2; + trmax[2] += 8; + VectorSubtract(trmax,ent->s.origin,temppos); + yaw = temppos[2]; + temppos[2] = 0; + x = VectorLength(temppos); + +//gi.bprintf(PRINT_HIGH,"ponko2! %f < %f\n",x,dist); + + if( x < dist /*MOVE_SPD_RUN*/) + { + dist = x; +// goto VCHCANSEL; //if center position move cancel + } + } + if(trace_priority < TRP_MOVEKEEP) zc->moveyaw = Get_yaw(temppos); + } + goto GOMOVE; + } + //wait for door open + else if(zc->zcstate & STS_W_DOOROPEN) + { + if(!trace_priority + || zc->waitin_obj->moveinfo.state == PSTATE_TOP + /*|| (zc->waitin_obj->moveinfo.state == PSTATE_DOWN)*/) + { +//gi.bprintf(PRINT_HIGH,"release %i %i\n",trace_priority,zc->waitin_obj->moveinfo.state); + zc->zcstate &= ~STS_WAITS; + zc->waitin_obj = NULL; + } + else if(zc->waitin_obj->moveinfo.state == PSTATE_BOTTOM + || zc->waitin_obj->moveinfo.state == PSTATE_UP) + { + VectorSubtract(zc->movtarget_pt,ent->s.origin,temppos); + temppos[2] = 0; + x = VectorLength(temppos); + dist *= 0.25; + if(x < 10 || VectorCompare(ent->s.origin,zc->movtarget_pt)) + { +// if(abs(zc->waitin_obj->s.origin[2] - zc->waitin_obj->s.old_origin[2]) == 0) goto VCHCANSEL; + + if(!zc->waitin_obj->union_ent) + { + trmin[0] = (zc->waitin_obj->absmin[0] + zc->waitin_obj->absmax[0]) / 2; + trmin[1] = (zc->waitin_obj->absmin[1] + zc->waitin_obj->absmax[1]) / 2; + trmin[2] = (zc->waitin_obj->absmin[2] + zc->waitin_obj->absmax[2]) / 2; + } + else VectorCopy(zc->waitin_obj->union_ent->s.origin,trmin); + trmin[2] += 8; + VectorSubtract(trmin,ent->s.origin,temppos); + if(trace_priority < TRP_MOVEKEEP) zc->moveyaw = Get_yaw(temppos); + if(trace_priority < TRP_ANGLEKEEP) + { + ent->s.angles[YAW] = zc->moveyaw; + ent->s.angles[PITCH] = Get_pitch(temppos); + } + goto VCHCANSEL; + } + else + { + if(trace_priority < TRP_MOVEKEEP) zc->moveyaw = Get_yaw(temppos); + if(!zc->waitin_obj->union_ent) + { + trmin[0] = (zc->waitin_obj->absmin[0] + zc->waitin_obj->absmax[0]) / 2; + trmin[1] = (zc->waitin_obj->absmin[1] + zc->waitin_obj->absmax[1]) / 2; + trmin[2] = (zc->waitin_obj->absmin[2] + zc->waitin_obj->absmax[2]) / 2; + } + else VectorCopy(zc->waitin_obj->union_ent->s.origin,trmin); + + trmin[2] += 8; + VectorSubtract(trmin,ent->s.origin,temppos); + if(trace_priority < TRP_ANGLEKEEP) + { + ent->s.angles[YAW] = Get_yaw(temppos); + ent->s.angles[PITCH] = Get_pitch(temppos); + } + } + } + } + +//LADDER + + + //-------------------------------------------------------------------------------------- + //ladder check +/* front = NULL, left = NULL, right = NULL; + k = false; + if(zc->route_trace) + { + Get_RouteOrigin(zc->routeindex,v); + if((v[2] - ent->s.origin[2]) >= 32) k = true; + + } + if(k && !zc->first_target && !zc->second_target && !(ent->client->ps.pmove.pm_flags & PMF_DUCKED)) + { + tempflag = false; + + VectorCopy(ent->mins,trmin); + VectorCopy(ent->maxs,trmax); + + trmin[2] += 20; + + //front + f1 = 32; + if(zc->route_trace) f1 = 32; + + iyaw = zc->moveyaw; + yaw = iyaw * M_PI * 2 / 360; + touchmin[0] = cos(yaw) * f1;//28 ; + touchmin[1] = sin(yaw) * f1; + touchmin[2] = 0; + + VectorAdd(ent->s.origin,touchmin,touchmax); + rs_trace = gi.trace (ent->s.origin, trmin,ent->maxs, touchmax,ent, MASK_BOTSOLID ); + front = rs_trace.ent; + + if(rs_trace.contents & CONTENTS_LADDER) tempflag = true; + + //right + if(!tempflag) + { + iyaw = zc->moveyaw + 90; + if(iyaw > 180) iyaw -= 360; + yaw = iyaw * M_PI * 2 / 360; + touchmin[0] = cos(yaw) * 32 ; + touchmin[1] = sin(yaw) * 32 ; + touchmin[2] = 0; + + VectorAdd(ent->s.origin,touchmin,touchmax); + rs_trace = gi.trace (ent->s.origin, trmin,ent->maxs, touchmax,ent, MASK_BOTSOLID ); + right = rs_trace.ent; + + if(rs_trace.contents & CONTENTS_LADDER) tempflag = true; + } + //left + if(!tempflag) + { + iyaw = zc->moveyaw - 90; + if(iyaw < -180) iyaw += 360; + yaw = iyaw * M_PI * 2 / 360; + touchmin[0] = cos(yaw) * 32 ; + touchmin[1] = sin(yaw) * 32 ; + touchmin[2] = 0; + + VectorAdd(ent->s.origin,touchmin,touchmax); + rs_trace = gi.trace (ent->s.origin, trmin,ent->maxs, touchmax,ent, MASK_BOTSOLID ); + left = rs_trace.ent; + + if(rs_trace.contents & CONTENTS_LADDER) tempflag = true; + } + + //ladder + if(tempflag) + { + VectorCopy(rs_trace.endpos,trmax); + VectorCopy(trmax,touchmax); + touchmax[2] += 8190; + rs_trace = gi.trace (temppos, trmin,ent->maxs, touchmax,ent, MASK_BOTSOLID ); + + VectorCopy(rs_trace.endpos,temppos); + VectorAdd(rs_trace.endpos,touchmin,touchmax); + rs_trace = gi.trace (temppos, trmin,ent->maxs, touchmax,ent, MASK_BOTSOLID); + + if(!(rs_trace.contents & CONTENTS_LADDER) && rs_trace.fraction) + { +// gi.WriteByte (svc_temp_entity); +// gi.WriteByte (TE_RAILTRAIL); +// gi.WritePosition (ent->s.origin); +// gi.WritePosition (temppos); +// gi.multicast (ent->s.origin, MULTICAST_PHS); + + ent->velocity[0] = 0; + ent->velocity[1] = 0; + if(zc->moveyaw == iyaw) + { +gi.bprintf(PRINT_HIGH,"ladder On!\n"); + ent->s.angles[YAW] = zc->moveyaw; + VectorCopy(trmax,ent->s.origin); + zc->zcstate |= STS_LADDERUP; + ent->s.angles[YAW] = zc->moveyaw; + ent->s.angles[PITCH] = -29; + + if(ent->waterlevel > 1) + { + ent->velocity[2] = VEL_BOT_WLADRUP; +// if(VectorCompare(ent->s.origin,ent->s.old_origin)) ent->velocity[2] += 50; + } + else + { + ent->velocity[2] = VEL_BOT_LADRUP; +// if(VectorCompare(ent->s.origin,ent->s.old_origin)) ent->velocity[2] += 50; + } +// gi.bprintf(PRINT_HIGH,"ladder!!\n"); + goto VCHCANSEL; + } + else + { + zc->moveyaw = iyaw; + ent->s.angles[YAW] = zc->moveyaw; + } + goto VCHCANSEL; + } + } + } + +*/ + //-------------------------------------------------------------------------------------- + //rocket jump + // ent->client->weaponstate = WEAPON_READY; + if (ent->groundentity && ent->client->weaponstate == WEAPON_READY && zc->second_target + && trace_priority < TRP_ANGLEKEEP) + { + if((zc->second_target->s.origin[2] - ent->s.origin[2]) > 100 + && ent->health > 70 && ent->waterlevel <=1 ) + { + j = false; + v[0] = ent->s.origin[0]; + v[1] = ent->s.origin[1]; + v[2] = zc->second_target->s.origin[2]; + rs_trace = gi.trace(v,NULL,NULL,zc->second_target->s.origin,zc->second_target,MASK_SOLID); + if(rs_trace.fraction == 1.0) j = true; + + VectorSubtract (zc->second_target->s.origin,ent->s.origin,trmin); + VectorCopy(trmin,trmax); + trmax[2] = 0; + it = Fdi_ROCKETLAUNCHER;//FindItem("Rocket Launcher"); + i = ITEM_INDEX(Fdi_ROCKETS/*FindItem("Rockets")*/); + +//ent->client->pers.inventory[ITEM_INDEX(it)] = 1; +//ent->client->pers.inventory[ent->client->ammo_index] = 1; + if( VectorLength(trmax) < 280 && canrocj && j) + { + VectorNormalize (trmin); + ent->s.angles[YAW] = Get_yaw (trmin); + zc->moveyaw = ent->s.angles[YAW]; + ent->s.angles[PITCH] = 90; + trmin[2] -= 24; + VectorNormalize (trmin); +// it->use(ent,it); + ent->client->pers.weapon = it; + ent->client->ammo_index = i; + ShowGun(ent); + + ent->velocity[2] += VEL_BOT_JUMP; + zc->zcstate |= STS_ROCJ; + gi.sound(ent, CHAN_VOICE, gi.soundindex("*jump1.wav"), 1, ATTN_NORM, 0); + PlayerNoise(ent, ent->s.origin, PNOISE_SELF); //pon + Set_BotAnim(ent,ANIM_JUMP,FRAME_jump1-1,FRAME_jump6); + ent->client->buttons |= BUTTON_ATTACK; + goto VCHCANSEL; //移動処理キャンセル + } + else zc->second_target = NULL; + } + else if((zc->second_target->s.origin[2] - ent->s.origin[2]) > 100) + { + if(ent->health <= 70 ) zc->second_target = NULL; + } + } + + //-------------------------------------------------------------------------------------- + //bot move to moveyaw +GOMOVE: + //jumping ====================================================== + if(!ent->groundentity && !ent->waterlevel && !ent->client->zc.trapped) + { + if(ent->velocity[2] > VEL_BOT_JUMP && !(zc->zcstate & STS_SJMASKEXW)) ent->velocity[2] = VEL_BOT_JUMP; + + k = false; + if(ent->client->ps.pmove.pm_flags & PMF_DUCKED) k = true; + for( x = 0 ; x < 90; x += 10) + { + dist = MOVE_SPD_RUN * ent->moveinfo.speed; + //right trace + yaw = zc->moveyaw + x; + if(yaw > 180) yaw -= 360; + i = Bot_moveT(ent,yaw,temppos,dist,&bottom); + if(i)// true || (i == 2 && ent->velocity > 0)) + { + if(bottom <= 24 && bottom > 0 && ent->velocity[2] <= 10 /*&& i == true*/) + { + //if(ent->velocity[2] > 0 || bottom >= 0) + VectorCopy(temppos,ent->s.origin); + break; + } + //turbo + if(!ent->waterlevel && ent->s.origin[2] > ent->s.old_origin[2] + && zc->route_trace + && !(zc->zcstate & STS_LADDERUP) + && !(zc->zcstate & STS_SJMASK) + && (zc->routeindex + 1) < CurrentIndex + && ent->velocity[2] >= 100 + && ent->velocity[2] < (100 + ent->gravity * sv_gravity->value * FRAMETIME)) + { + Get_RouteOrigin(zc->routeindex ,v); + Get_RouteOrigin(zc->routeindex + 1,vv); + k = 0; + + j = Bot_moveT(ent,yaw,trmin,16,&f1); + VectorSubtract(v,ent->s.origin,trmin); + if((vv[2] - v[2]) > JumpMax) k = 1; + else if((v[2] - ent->s.origin[2]) > JumpMax) k = 2; + else if(!TargetJump_Chk(ent,vv,0) && VectorLength(trmin) < 64) + { +//gi.bprintf(PRINT_HIGH,"dist %f!!\n",VectorLength(trmin)); + if(TargetJump_Chk(ent,vv,ent->velocity[2])) k = 1; + } + + + if(!j) k = 0; + else if( f1 > 10 && f1 < -10) k = 0; + if(k) + { + if(k == 2) VectorCopy(v,vv); + if(TargetJump_Turbo(ent,vv)) + { +//gi.bprintf(PRINT_HIGH,"speed %f!!\n",ent->moveinfo.speed); +//if(ent->velocity[2] > (VEL_BOT_JUMP + 100 + ent->gravity * sv_gravity->value * FRAMETIME )) +// ent->velocity[2] = VEL_BOT_JUMP + 100 + ent->gravity * sv_gravity->value * FRAMETIME; + VectorSubtract(vv,ent->s.origin,v); + zc->moveyaw = Get_yaw(v); + if(ent->velocity[2] > VEL_BOT_JUMP) zc->zcstate |= STS_TURBOJ; + if(k == 1) zc->routeindex++; + break; + } + } + } +// bottom > 0else ent->moveinfo.speed = 0.2; + if(bottom <= 0) + { + VectorCopy(temppos,ent->s.origin); + if(i == 2 /*&& k*/) ent->client->ps.pmove.pm_flags |= PMF_DUCKED; + else ent->client->ps.pmove.pm_flags &= ~PMF_DUCKED; + break; + } + else + { + ent->moveinfo.speed = 0.3;//0.2; + } + } + else + { + ent->moveinfo.speed = 0.3;//0.2; + } + + if(x == 0) continue; + //left trace + yaw = zc->moveyaw - x; + if(yaw < -180) yaw += 360; + i = Bot_moveT(ent,yaw,temppos,dist,&bottom); + if(i )//== true || (i == 2 && ent->velocity > 0)) + { + if(bottom <= 24 && bottom >0 && ent->velocity[2] <= 10 /*&& i == true*/) + { + //if(ent->velocity[2] > 0 || bottom >= 0) + VectorCopy(temppos,ent->s.origin); + break; + } + //turbo + if(!ent->waterlevel && ent->s.origin[2] > ent->s.old_origin[2] + && zc->route_trace + && !(zc->zcstate & STS_LADDERUP) + && !(zc->zcstate & STS_SJMASK) + && (zc->routeindex + 1) < CurrentIndex + && ent->velocity[2] >= 100 + && ent->velocity[2] < (100 + ent->gravity * sv_gravity->value * FRAMETIME)) + { + Get_RouteOrigin(zc->routeindex ,v); + Get_RouteOrigin(zc->routeindex + 1,vv); + k = 0; + + j = Bot_moveT(ent,yaw,trmin,16,&f1); + VectorSubtract(v,ent->s.origin,trmin); + if((vv[2] - v[2]) > JumpMax) k = 1; + else if((v[2] - ent->s.origin[2]) > JumpMax) k = 2; + else if(!TargetJump_Chk(ent,vv,0) && VectorLength(trmin) < 64) + { +//gi.bprintf(PRINT_HIGH,"dist %f!!\n",VectorLength(trmin)); + if(TargetJump_Chk(ent,vv,ent->velocity[2])) k = 1; + } + + if(!j) k = 0; + else if( f1 > 10 && f1 < -10) k = 0; + if(k ) + { + if(k == 2) VectorCopy(v,vv); + if(TargetJump_Turbo(ent,vv)) + { +//gi.bprintf(PRINT_HIGH,"speed %f!!\n",ent->moveinfo.speed); +//if(ent->velocity[2] > (VEL_BOT_JUMP + 100 + ent->gravity * sv_gravity->value * FRAMETIME )) +// ent->velocity[2] = VEL_BOT_JUMP + 100 + ent->gravity * sv_gravity->value * FRAMETIME; +//if(ent->moveinfo.speed < 0.5) ent->moveinfo.speed = 0.5; + VectorSubtract(vv,ent->s.origin,v); + zc->moveyaw = Get_yaw(v); + if(ent->velocity[2] > VEL_BOT_JUMP) zc->zcstate |= STS_TURBOJ; + if(k == 1) zc->routeindex++; + break; + } + } + } +// else ent->moveinfo.speed = 0.2; + if(bottom <= 0) + { + VectorCopy(temppos,ent->s.origin); + if(i == 2 /*&& k*/) ent->client->ps.pmove.pm_flags |= PMF_DUCKED; + else ent->client->ps.pmove.pm_flags &= ~PMF_DUCKED; + break; + } + else ent->moveinfo.speed = 0.3;//0.2; + } + else ent->moveinfo.speed = 0.3;//0.2; + } + if(x >= 90 /*&& ent->velocity[2] < 0*/) + { +//gi.bprintf(PRINT_HIGH,"jump fail!\n"); + if(trace_priority < TRP_ANGLEKEEP) ent->s.angles[YAW] += ((random() - 0.5) * 360); + if(ent->s.angles[YAW]>180) ent->s.angles[YAW] -= 360; + else if(ent->s.angles[YAW]< -180) ent->s.angles[YAW] += 360; + } + goto VCHCANSEL; + } + + // on ground or in water ====================================================== + waterjumped = false; + if(ent->groundentity || ent->waterlevel ) + { + if(ent->groundentity && /*zc->waterstate == WAS_NONE*/ent->waterlevel <= 0 ) k = 1; + else if(ent->waterlevel) + { + k = 2; + if(zc->route_trace) + { + Get_RouteOrigin(zc->routeindex,v); + VectorSubtract(v,ent->s.origin,vv); + vv[2] = 0; + if(v[2] < ent->s.origin[2] && VectorLength(vv) < 24) k = 0; + } + if(ent->waterlevel == 3) k = 0; + } + else if(ent->waterlevel) k = 0; + else k = 1; + if(k) if(ent->client->ps.pmove.pm_flags & PMF_DUCKED) k = 0; + + if(zc->waterstate) f1 = BOTTOM_LIMIT_WATER; + else f1 = - JumpMax;//BOTTOM_LIMIT;//dropable height + + if(zc->nextcheck < (level.time + FRAMETIME * 10)) + { + VectorSubtract(zc->pold_origin,ent->s.origin,temppos); + if(VectorLength(temppos) < 64) + { + if(zc->route_trace) + { + if(!chedit->value) + { + zc->route_trace = false; + zc->routeindex++; + } + zc->second_target = NULL; + } + else f1 = BOTTOM_LIMITM; + } + + if(zc->nextcheck < level.time) + { + VectorCopy(ent->s.origin,zc->pold_origin); + zc->nextcheck = level.time + FRAMETIME * 40; + } + } + f3 = 20; //movablegap + //this v not modify till do special + if(zc->route_trace) Get_RouteOrigin(zc->routeindex,v); + + if(ent->waterlevel && zc->route_trace) + { + if(v[2] + 20 <= ent->s.origin[2]) + { + f2 = 20,f3 = 0; + } + else + { + if(zc->waterstate /*== WAS_FLOAT*/) f2 = JumpMax;//TOP_LIMIT_WATER; + else f2 = JumpMax; + } + } + else + { + if(zc->waterstate /*== WAS_FLOAT*/) f2 = JumpMax;//TOP_LIMIT_WATER; + else f2 = JumpMax; + } + +//if(ent->client->ps.pmove.pm_flags & PMF_DUCKED) gi.bprintf(PRINT_HIGH,"cycle!\n"); + ladderdrop = true; + for( x = 0 ; x <= 180 && dist != 0; x += 10) + { + //right trace + yaw = zc->moveyaw + x; + if(yaw > 180) yaw -= 360; + if(j = Bot_moveT (ent,yaw,temppos,dist,&bottom)) + { + //special + if(x == 0 && /*bottom < 20 &&*/ !ent->waterlevel + && !(ent->client->ps.pmove.pm_flags & PMF_DUCKED)) + { + if( zc->second_target) + { + if(((zc->second_target->s.origin[2] + 8 ) - (ent->s.origin[2] + bottom)) > f2) + { + if(Bot_Fall(ent,temppos,dist)) + { + ent->client->ps.pmove.pm_flags &= ~PMF_DUCKED; + break; + } + } + } + else if( zc->route_trace) + { +// Get_RouteOrigin(zc->routeindex,v); +// FRAMETIME * (ent->velocity[2] - ent->gravity * sv_gravity->value * FRAMETIME) + if((v[2] - (ent->s.origin[2] + bottom )) > f2 || + (bottom > 20 && v[2] > ent->s.origin[2])) + { + ladderdrop = false; + if(Bot_Fall(ent,temppos,dist) && !zc->waterstate) + { + ent->client->ps.pmove.pm_flags &= ~PMF_DUCKED; + break; + } + if((v[2] - ent->s.origin[2]) <= JumpMax) + { + if(Route[zc->routeindex].state == GRS_ONTRAIN && zc->waterstate < WAS_IN) break; + if(zc->routeindex > 0) + if(Route[zc->routeindex - 1].state == GRS_ONTRAIN + && Route[zc->routeindex - 1].ent == ent->groundentity) break; + } + } + else if(ent->groundentity) + { +//if(Q_stricmp (ent->groundentity->classname,"worldspawn")) +//gi.bprintf(PRINT_HIGH,"%s!\n",ent->groundentity->classname); + if(!Q_stricmp (ent->groundentity->classname,"func_rotating")) + { + if(Bot_Fall(ent,temppos,dist)) + { + ent->client->ps.pmove.pm_flags &= ~PMF_DUCKED; + break; + } + } + else if(Route[zc->routeindex].state == GRS_ONROTATE) + { + if(!TraceX(ent,v) || !HazardCheck(ent,v)) break; + if(!BankCheck(ent,v)) break; + if(Bot_Fall(ent,temppos,dist)) + { + ent->client->ps.pmove.pm_flags &= ~PMF_DUCKED; + break; + } + } + if(0/*dist < 16 && v[2] < (ent->s.origin[2] - 24) + && !(zc->zcstate & STS_WAITSMASK) && ent->waterlevel*/) + { + if(Bot_moveT (ent,yaw,trmin,32,&iyaw)) + { + if(iyaw < 0) + { + ent->moveinfo.speed = 0.05; + VectorCopy(trmin,ent->s.origin); + break; + } + } + } + } + } + } + + //jumpable1 + if(/*((bottom > 20 && !ent->waterlevel) || (bottom > 0 && (ent->waterlevel == 2 || (ent->waterlevel == 1 && ent->groundentity == NULL))))*/ + bottom > 20 + && bottom <= f2 && j == true && k + && !(ent->client->ps.pmove.pm_flags & PMF_DUCKED)) + { + ent->moveinfo.speed = 0.15; + if(k == 1/*!ent->waterlevel*/) + { + ent->velocity[2] += VEL_BOT_JUMP; + gi.sound(ent, CHAN_VOICE, gi.soundindex("*jump1.wav"), 1, ATTN_NORM, 0); + PlayerNoise(ent, ent->s.origin, PNOISE_SELF); //pon + } + else + { + ent->moveinfo.speed = 0.1; + //waterjumped = true; + if(ent->velocity[2] < VEL_BOT_WJUMP/*=1*/ || VectorCompare(ent->s.origin,ent->s.old_origin)) + { + ent->velocity[2] /*+*/= VEL_BOT_WJUMP;//(/*VEL_BOT_WJUMP*/ 110 /*+ bottom*/); + zc->zcstate |= STS_WATERJ; + goto VCHCANSEL; + } + goto VCHCANSEL; + } + Set_BotAnim(ent,ANIM_JUMP,FRAME_jump1-1,FRAME_jump6); + zc->moveyaw = yaw; + ent->client->ps.pmove.pm_flags &= ~PMF_DUCKED; + break; + } + //dropable1 + else if(bottom <= f3 &&(bottom >= f1 || /*zc->waterstate*/ent->waterlevel /* 2*/)) + { +// ent->client->anim_priority = ANIM_BASIC; + if(bottom < 0 && !zc->waterstate/*(ent->waterlevel && !zc->waterstate/*ent->waterlevel < 2)*/) + { + f2 = FRAMETIME * (ent->velocity[2] - ent->gravity * sv_gravity->value * FRAMETIME); + if(bottom >= f2 && ent->velocity[2] < 0/*20*/) temppos[2] += bottom; + else temppos[2] += f2;//20; + } + VectorCopy(temppos,ent->s.origin); + if(f1 > BOTTOM_LIMIT) ent->moveinfo.speed = 0.25; + if(j != true) + { +//gi.bprintf(PRINT_HIGH,"ducked1!!\n"); + ent->client->ps.pmove.pm_flags |= PMF_DUCKED; + } + else ent->client->ps.pmove.pm_flags &= ~PMF_DUCKED; + + if(x > 30 || !zc->route_trace) + { + f2 = zc->moveyaw; + zc->moveyaw = yaw; + if(f2 == ent->s.angles[YAW] && trace_priority < TRP_ANGLEKEEP) ent->s.angles[YAW] = yaw; + } + break; + } + //dropable?1 + else if ( bottom < f1 && !zc->waterstate/*!ent->waterlevel*/ && x <= 30) + { + if( ladderdrop && zc->ground_contents & CONTENTS_LADDER && bottom != -9999) + { + VectorCopy(temppos,ent->s.origin); + zc->moveyaw = yaw; + ent->moveinfo.speed = 0.2; + goto VCHCANSEL; + } + + if( ladderdrop && bottom < 0 && !zc->waterstate/*!ent->waterlevel*/) + { + if(Bot_moveW ( ent,yaw,temppos,dist,&bottom)) + { + if(zc->second_target) iyaw = zc->second_target->s.origin[2] - ent->s.origin[2]; + else iyaw = -41; + if(bottom > -20 && iyaw < -40) + { + VectorCopy(temppos,ent->s.origin); + break; + } + } + } + //fall1 + if(Bot_Fall(ent,temppos,dist)) + { +// ent->client->ps.pmove.pm_flags &= ~PMF_DUCKED; +//gi.bprintf(PRINT_HIGH,"drop!\n"); + break; + } + } + } +//else if(ent->client->zc.waterstate == 1 && x == 0) gi.bprintf(PRINT_HIGH,"maaaap %i\n",j); + + if(x == 0 && (zc->battlemode & FIRE_SHIFT)) zc->battlemode &= ~FIRE_SHIFT; + if(x == 0 || x == 180) continue; + //left trace + yaw = zc->moveyaw - x; + if(yaw < -180) yaw += 360; + if(j = Bot_moveT (ent,yaw,temppos,dist,&bottom)) + { + if(zc->waterstate == WAS_FLOAT) f2 = TOP_LIMIT_WATER; + else f2 = JumpMax; + //jumpable2 + if(/*((bottom > 20 && !ent->waterlevel) || (bottom > 0 && (ent->waterlevel == 2 || (ent->waterlevel == 1 && ent->groundentity == NULL))) )*/ + bottom > 20 + && bottom <= f2 && j == true && k + && !(ent->client->ps.pmove.pm_flags & PMF_DUCKED)) + { + ent->moveinfo.speed = 0.15; + if(k == 1/*!ent->waterlevel*/) + { + ent->velocity[2] += VEL_BOT_JUMP; + gi.sound(ent, CHAN_VOICE, gi.soundindex("*jump1.wav"), 1, ATTN_NORM, 0); + PlayerNoise(ent, ent->s.origin, PNOISE_SELF); //pon + } + else + { + ent->moveinfo.speed = 0.1; + //waterjumped = true; + if(ent->velocity[2] < VEL_BOT_WJUMP/*= 1*/ || VectorCompare(ent->s.origin,ent->s.old_origin)) + { + ent->velocity[2] /*+*/= VEL_BOT_WJUMP;//(/*VEL_BOT_WJUMP*/ 110 /*+ bottom*/); + zc->zcstate |= STS_WATERJ; + goto VCHCANSEL; + } + goto VCHCANSEL; + } + Set_BotAnim(ent,ANIM_JUMP,FRAME_jump1-1,FRAME_jump6); + zc->moveyaw = yaw; + ent->client->ps.pmove.pm_flags &= ~PMF_DUCKED; + break; + } + //dropable2 + else if(bottom <= f3 && (bottom >= f1 || ent->waterlevel /* 2zc->waterstate*/)) + { + //ent->client->anim_priority = ANIM_BASIC; + if(bottom < 0 && !zc->waterstate/*(ent->waterlevel && !zc->waterstate/*ent->waterlevel < 2)*/) + { +//gi.bprintf(PRINT_HIGH,"ponko\n"); + f2 = FRAMETIME * (ent->velocity[2] - ent->gravity * sv_gravity->value * FRAMETIME); + if(bottom >= f2 && ent->velocity[2] < 0/*20*/) temppos[2] += bottom; + else temppos[2] += f2;//20; + } + VectorCopy(temppos,ent->s.origin); + if(f1 > BOTTOM_LIMIT) ent->moveinfo.speed = 0.25; + if(j != true) + { +//gi.bprintf(PRINT_HIGH,"ducked2!!\n"); + ent->client->ps.pmove.pm_flags |= PMF_DUCKED; + } + else ent->client->ps.pmove.pm_flags &= ~PMF_DUCKED; + + if(x > 30 || !zc->route_trace) + { + f2 = zc->moveyaw; + zc->moveyaw = yaw; + if(f2 == ent->s.angles[YAW] && trace_priority < TRP_ANGLEKEEP) ent->s.angles[YAW] = yaw; + } + break; + } + //dropable?2 + else if (bottom < f1 && !zc->waterstate/*!ent->waterlevel*/ && x <= 30) + { + if( ladderdrop && zc->ground_contents & CONTENTS_LADDER && bottom != -9999) + { + VectorCopy(temppos,ent->s.origin); + zc->moveyaw = yaw; + ent->moveinfo.speed = 0.2; + goto VCHCANSEL; + } + + if( ladderdrop && bottom < 0 && !zc->waterstate/*!ent->waterlevel*/) + { + if(Bot_moveW ( ent,yaw,temppos,dist,&bottom)) + { + if(zc->second_target) iyaw = zc->second_target->s.origin[2] - ent->s.origin[2]; + else iyaw = -41; + if(bottom > -54 && iyaw < -40) + { + VectorCopy(temppos,ent->s.origin); + break; + } + } + } + + //fall2 + if(Bot_Fall(ent,temppos,dist)) + { +// ent->client->ps.pmove.pm_flags &= ~PMF_DUCKED; + break; + } + } + } + } + + if(!zc->route_trace && zc->first_target == NULL) + { + if(trace_priority < TRP_ANGLEKEEP) ent->s.angles[YAW] = yaw; + } + + if(x >= 70) + { + if(zc->second_target) zc->second_target = NULL; +/* else if(!zc->route_trace && zc->first_target == NULL) + { + if(trace_priority < TRP_ANGLEKEEP) ent->s.angles[YAW] = yaw; + }*/ + else if( 0/*zc->route_trace*/) + { +//gi.bprintf(PRINT_HIGH,"OFF 9\n"); //ppx + k = false; + if( x > 90 && ent->groundentity) + { + if(!Q_stricmp (ent->groundentity->classname,"func_train")) k = true; + } + else if( x > 90 && Route[zc->routeindex].state == GRS_ONTRAIN) k = true; + if(k && trace_priority < TRP_ANGLEKEEP) + { + VectorCopy(Origin,ent->s.origin); + VectorCopy(Velocity,ent->velocity); + ent->s.angles[YAW] = OYaw; + goto VCHCANSEL; + } +/* if(!k) + { + if(++zc->routeindex >= CurrentIndex) zc->routeindex = 0; + zc->route_trace = false; + }*/ + } + } + + if(/*zc->waterstate*/ent->waterlevel && !waterjumped) + { + k = false; + VectorCopy(ent->s.origin,temppos); +// temppos[2] += 26; +// i = gi.pointcontents (temppos); + if(zc->second_target != NULL) + { + k = 2; + x = zc->second_target->s.origin[2] - ent->s.origin[2]; + if(x > 13/*8*/) x = 13;//8; + else if(x < -13/*8*/) x = -13;//8; + if(x < 0)//アイテム下方 + { + if( Bot_Watermove (ent,temppos,dist,x)) + { + VectorCopy(temppos,ent->s.origin); + k = true; + } + } + else if(x >0 && zc->waterstate == WAS_IN + && !(ent->client->ps.pmove.pm_flags & PMF_DUCKED)) //アイテム上方 + { + if(ent->velocity[2] < 0) ent->velocity[2] = 0; + if( Bot_Watermove (ent,temppos,dist,x)) + { + VectorCopy(temppos,ent->s.origin); + k = true; + } + } + } + else if(zc->route_trace ) + { + Get_RouteOrigin(zc->routeindex,v); + + k = 2; + x = v[2] - ent->s.origin[2]; + if(x > 13/*8*/) x = 13;//8; + else if(x < -13/*8*/) x = -13;//8; + if(x < 0)//アイテム下方 + { + if( Bot_Watermove (ent,temppos,dist,x)) + { +//gi.bprintf(PRINT_HIGH,"Down! %f\n",x); + VectorCopy(temppos,ent->s.origin); + k = true; + } + } + else if(x > 0 && zc->waterstate == WAS_IN + && !(ent->client->ps.pmove.pm_flags & PMF_DUCKED)) //アイテム上方 + { +//gi.bprintf(PRINT_HIGH,"UP! %f\n",x); + if(ent->velocity[2] < -10) ent->velocity[2] = 0; + if( Bot_Watermove (ent,temppos,dist,x)) + { + VectorCopy(temppos,ent->s.origin); + k = true; + } + } + else if(x == 0) + { +//gi.bprintf(PRINT_HIGH,"ZERO! %f\n",x); +// VectorSubtract(v,ent->s.origin,vv); +// if(VectorLength(vv) < 13) VectorCopy(v,ent->s.origin); + } + } + else if((ent->air_finished - FRAMETIME * 20 ) < level.time + && zc->waterstate == WAS_IN) + { + if( Bot_Watermove (ent,temppos,dist,13/*8*/)) + { + VectorCopy(temppos,ent->s.origin); + k = true; + } + else k = 2; + } + + if(k == true) Get_WaterState(ent); + if(zc->route_trace && v[2] == ent->s.origin[2]) k = 3; + + if((!ent->groundentity && !zc->waterstate && k && ent->velocity[2] < 1) + ||(zc->waterstate == WAS_IN && (ent->client->ps.pmove.pm_flags & PMF_DUCKED))) + { + if( Bot_Watermove (ent,temppos,dist,-7/*8*/) && k != 3) + { + VectorCopy(temppos,ent->s.origin); + } + } + if(zc->waterstate == WAS_IN) ent->moveinfo.decel = level.time; + else if(!k) //水面にずっといたとき + { + if( ( level.time - ent->moveinfo.decel) > 4.0 && !zc->route_trace) + { + ent->velocity[2] = -200; + ent->moveinfo.decel = level.time; + } + } + + if(ent->groundentity && ent->waterlevel == 1) + { + VectorSubtract(ent->s.origin,ent->s.old_origin,temppos); + if(!temppos[0] && !temppos[1] && !temppos[2]) ent->velocity[2] += 80; + } + } + //not in water + else if(zc->route_trace && !dist) + { + Get_RouteOrigin(zc->routeindex,v); + if(v[2] < (ent->s.origin[2] - 20)) + { + if( Bot_Watermove (ent,temppos,dist,-20)) + { + VectorCopy(temppos,ent->s.origin); + } + } + } + + } + //sticking +/* if(VectorCompare(ent->s.origin,ent->s.old_origin) && ent->waterlevel) + { + ent->velocity[2] += 200; + } +*/ + //-------------------------------------------------------------------------------------- + //player check door and corner + if(!zc->route_trace && trace_priority && !zc->second_target && random() < 0.2) + { + VectorCopy(ent->s.origin,v); + VectorCopy(ent->mins,touchmin); + touchmin[2] += 16; + VectorCopy(ent->maxs,touchmax); + if(ent->client->ps.pmove.pm_flags & PMF_DUCKED) touchmax[2] = 0; + else v[2] += 20; + + //right + if(random() < 0.5) + { + f1 = zc->moveyaw + 90; + if(f1 > 180) iyaw -= 360; + f2 = zc->moveyaw + 135; + if(f2 > 180) iyaw -= 360; + } + //left + else + { + f1 = zc->moveyaw - 90; + if(f1 < 180) iyaw += 360; + f2 = zc->moveyaw - 135; + if(f2 < 180) iyaw += 360; + } + + yaw = f1 * M_PI * 2 / 360; + trmin[0] = cos(yaw) * 128 ; + trmin[1] = sin(yaw) * 128 ; + trmin[2] = 0; + VectorAdd(v,trmin,trmax); + rs_trace = gi.trace (v, NULL,NULL, trmax,ent, MASK_BOTSOLIDX/*MASK_PLAYERSOLID*/ ); + x = rs_trace.fraction; + + yaw = f2 * M_PI * 2 / 360; + trmin[0] = cos(yaw) * 128 ; + trmin[1] = sin(yaw) * 128 ; + trmin[2] = 0; + VectorAdd(v,trmin,trmax); + rs_trace = gi.trace (v, NULL/*touchmin*/,NULL/*touchmax*/, trmax,ent, MASK_BOTSOLIDX/*MASK_PLAYERSOLID*/ ); + + if( x > rs_trace.fraction && x > 0.5) zc->moveyaw = f1; + } + + + //-------------------------------------------------------------------------------------- + //push button + + it_ent = NULL; + k = 0; + + VectorCopy (ent->absmin, touchmin); + VectorCopy (ent->absmax, touchmax); + + touchmin[0] -= 48;//32; + touchmin[1] -= 48;//32; + touchmin[2] -= 5; + touchmax[0] += 48;//32; + touchmax[1] += 48;//32; + i = gi.BoxEdicts ( touchmin ,touchmax,touch,MAX_EDICTS,AREA_SOLID); + + if(i) + { + for(j = i - 1;j >= 0;j--) + { + trent = touch[j]; + if(trent->classname) + { + if(!Q_stricmp (trent->classname,"func_button")) + { + k = 1; + it_ent = trent; + break; + } + else if(!Q_stricmp (trent->classname,"func_door") + || !Q_stricmp (trent->classname,"func_door_rotating")) + { + if(trent->targetname == NULL && !trent->takedamage && ent->groundentity != trent) + { + k = 2; + it_ent = trent; + break; + } + } + } + } + } + //when touch da button + if( it_ent != NULL && k == 1) + { + if(it_ent->use && it_ent->moveinfo.state == PSTATE_BOTTOM && !it_ent->health) + { + k = false; + if(zc->route_trace && zc->routeindex - 1 > 0) + { + k = true; + i = zc->routeindex; + if(Route[i].state == GRS_PUSHBUTTON) k = false; + else if(Route[--i].state == GRS_PUSHBUTTON) k = false; + + if(!k && Route[i].ent == it_ent) zc->routeindex = i + 1; + else k = true; + } + +// if(!k) buttonuse = true;//it_ent->use(it_ent,ent,it_ent/*ent*/); + if(!k && it_ent->target) + { + string = it_ent->target; + e = &g_edicts[(int)maxclients->value+1]; + for ( i=maxclients->value+1 ; iinuse || !e->targetname) continue; + if (Q_stricmp (string, e->targetname) == 0 ) + { +//gi.bprintf(PRINT_HIGH,"yea4 %i %s\n",e->moveinfo.state,e->classname); + if(e->classname[0] == 't') + { + if(!Q_stricmp (e->classname,"trigger_relay")) + { + if(e->target) + { + string = e->target; + e = &g_edicts[(int)maxclients->value]; + i=maxclients->value; + continue; + } + } + else if(!Q_stricmp (e->classname,"target_laser") + || !Q_stricmp (e->classname,"target_mal_laser")) + { + if(e->spawnflags & 1) + { + it_ent->use(it_ent,ent,it_ent); + break; + } + } + } + else if(e->classname[0] == 'f') + { + it_ent->use(it_ent,ent,it_ent/*ent*/); + if(!Q_stricmp (e->classname,"func_door") + || !Q_stricmp ( e->classname,"func_door_rotating") + /*&& (e->moveinfo.state == 1 || e->moveinfo.state == 2)*/ + )// && abs ((e->moveinfo.start_origin[2] - e->moveinfo.end_origin[2])) > 54 ) + { + k = false; + // return true; + if(!zc->route_trace /*|| zc->routeindex <= 0*/) + { + v[0] = (it_ent->absmin[0] + it_ent->absmax[0]) / 2; + v[1] = (it_ent->absmin[1] + it_ent->absmax[1]) / 2; + v[2] = (it_ent->absmin[2] + it_ent->absmax[2]) / 2; + VectorSubtract(it_ent->union_ent->s.origin,v,temppos); + VectorScale (temppos, 3, v); + VectorAdd(ent->s.origin,v,zc->movtarget_pt); + } + else + { + /*Get_RouteOrigin(zc->routeindex - 1,v); + VectorSubtract(v,ent->s.origin,temppos); + VectorScale (temppos, 3, v); + VectorCopy(ent->s.origin,temppos); + VectorAdd(ent->s.origin,v,zc->movtarget_pt);*/ + VectorCopy(ent->s.origin,zc->movtarget_pt); + } + //VectorScale (temppos, 3, v); + //VectorAdd(ent->s.origin,v,zc->movtarget_pt); + + if(fabs (e->moveinfo.start_origin[2] - e->moveinfo.end_origin[2]) > JumpMax ) + { + if(e->union_ent == NULL) + { +//gi.bprintf(PRINT_HIGH,"voodoo\n"); //ppx + it = FindItem("Roam Navi3"); + trent = G_Spawn(); + trent->classname = it->classname; + trent->s.origin[0] = (e->absmin[0] + e->absmax[0])/2; + trent->s.origin[1] = (e->absmin[1] + e->absmax[1])/2; + trent->s.origin[2] = e->absmax[2] + 16; + trent->union_ent = e; + e->union_ent = trent; + + //trent->nextthink = level.time + 10; + //trent->think = G_FreeEdict; + + SpawnItem3 (trent, it); + } + else + { + trent = e->union_ent; + trent->solid = SOLID_TRIGGER; + trent->svflags &= ~SVF_NOCLIENT; +//gi.bprintf(PRINT_HIGH,"SPAWNed\n"); //ppx + } +// SpawnItem2 (trent, it); + + zc->second_target = trent; + trent->target_ent = ent; + + //トグル式はすぐ走る + if(e->spawnflags & PDOOR_TOGGLE) + { + f1 = e->moveinfo.start_origin[2] - e->moveinfo.end_origin[2]; + //スタート地点が上 + if(f1 > 0 ) + { + k = true; + if(e->moveinfo.state == PSTATE_BOTTOM || e->moveinfo.state == PSTATE_UP ) + { + if((trent->s.origin[2] - ent->s.origin[2]) > 32) zc->second_target = NULL; + } + else if(e->moveinfo.state == PSTATE_TOP || e->moveinfo.state == PSTATE_DOWN) + { + if((trent->s.origin[2] - ent->s.origin[2]) < -48) zc->second_target = NULL; + } + } + else + { + k = true; + if(e->moveinfo.state == PSTATE_TOP || e->moveinfo.state == PSTATE_DOWN) + { + if((trent->s.origin[2] - ent->s.origin[2]) > 32) zc->second_target = NULL; + } + else if(e->moveinfo.state == PSTATE_BOTTOM || e->moveinfo.state == PSTATE_UP) + { + if((trent->s.origin[2] - ent->s.origin[2]) < -48) zc->second_target = NULL; + } + } + } + //ノーマル + else + { + f1 = e->moveinfo.start_origin[2] - e->moveinfo.end_origin[2]; + //スタート地点が上 + if(f1 > 0 ) + { + if(e->moveinfo.state == PSTATE_BOTTOM || e->moveinfo.state == PSTATE_UP) + { + if(fabs(trent->s.origin[2] - ent->s.origin[2]) < JumpMax) k = true; + } + } + else + { + if(e->moveinfo.state == PSTATE_BOTTOM || e->moveinfo.state == PSTATE_UP ) + { + if(fabs(trent->s.origin[2] - ent->s.origin[2]) < JumpMax) k = true; + } + } + } +// if(Bot_trace (ent,zc->second_target)) k = true; + } + if(!k) + { + //gi.bprintf(PRINT_HIGH,"waitset %i\n",e->moveinfo.state); + zc->waitin_obj = e; + zc->zcstate &= ~STS_WAITS; + zc->zcstate |= STS_W_DOOROPEN; + } + else + { + if((e->union_ent->s.origin[2] + 8 + - ent->s.origin[2]) > JumpMax) + { + zc->route_trace = false; + zc->zcstate &= ~STS_WAITS; + } + } + break; + } + } + } + } + } + else if(!k) it_ent->use(it_ent,ent,it_ent/*ent*/); + } + + } + //when touch da door + else if( it_ent != NULL && k == 2) + { + if(it_ent->moveinfo.state == PSTATE_BOTTOM) + { + if(it_ent->flags & FL_TEAMSLAVE) it_ent->teammaster->use(it_ent->teammaster,ent,it_ent->teammaster/*ent*/); + else it_ent->use(it_ent,ent,it_ent/*ent*/); + } + +//if(zc->zcstate & STS_WAITSMASK ) gi.bprintf(PRINT_HIGH,"Door Use\n"); + + if(it_ent->moveinfo.state == PSTATE_BOTTOM) + { + VectorCopy(ent->s.origin,zc->movtarget_pt); + zc->waitin_obj = it_ent; + zc->zcstate &= ~STS_WAITS; + zc->zcstate |= STS_W_DOOROPEN; + + if(it_ent->flags & FL_TEAMSLAVE) + { + trmin[0] = (it_ent->teammaster->absmin[0] + it_ent->teammaster->absmax[0]) / 2; + trmin[1] = (it_ent->teammaster->absmin[1] + it_ent->teammaster->absmax[1]) / 2; + trmax[0] = (it_ent->absmin[0] + it_ent->absmax[0]) / 2; + trmax[1] = (it_ent->absmin[1] + it_ent->absmax[1]) / 2; + + temppos[0] = (trmin[0] + trmax[0]) /2; + temppos[1] = (trmin[1] + trmax[1]) /2; + if(trace_priority < TRP_ANGLEKEEP) ent->s.angles[YAW] = Get_yaw(temppos); + } + else + { + trmax[0] = (it_ent->absmin[0] + it_ent->absmax[0]) / 2; + trmax[1] = (it_ent->absmin[1] + it_ent->absmax[1]) / 2; + VectorSubtract(trmax,ent->s.origin,temppos); + if(trace_priority < TRP_ANGLEKEEP) ent->s.angles[YAW] = Get_yaw(temppos); + } + } + else if(it_ent->moveinfo.state == PSTATE_UP ) + { + VectorCopy(ent->s.origin,zc->movtarget_pt); + zc->waitin_obj = it_ent; + zc->zcstate &= ~STS_WAITS; + zc->zcstate |= STS_W_DOOROPEN; + } + } +VCHCANSEL: + //-------------------------------------------------------------------------------------- + //ladder check + front = NULL, left = NULL, right = NULL; + k = false; + if(zc->route_trace && (zc->routeindex + 1) < CurrentIndex) + { + Get_RouteOrigin(zc->routeindex + 1,v); + if((v[2] - ent->s.origin[2]) >= 32 /*|| ent->waterlevel*/) k = true; + } + if(k && trace_priority && !zc->second_target && !(ent->client->ps.pmove.pm_flags & PMF_DUCKED)) + { + tempflag = false; + + VectorCopy(ent->mins,trmin); + VectorCopy(ent->maxs,trmax); + + trmin[2] += 20; + + //front + f1 = 32; + if(zc->route_trace) f1 = 32; + + iyaw = zc->moveyaw; + yaw = iyaw * M_PI * 2 / 360; + touchmin[0] = cos(yaw) * f1;//28 ; + touchmin[1] = sin(yaw) * f1; + touchmin[2] = 0; + + VectorAdd(ent->s.origin,touchmin,touchmax); + rs_trace = gi.trace (ent->s.origin, trmin,ent->maxs, touchmax,ent, MASK_BOTSOLID ); + front = rs_trace.ent; + + if(rs_trace.contents & CONTENTS_LADDER) tempflag = true; + + //upper + if(!tempflag && !zc->waterstate) + { + trmax[2] += 32; + rs_trace = gi.trace (ent->s.origin, trmin,trmax, touchmax,ent, MASK_BOTSOLID ); + if(rs_trace.contents & CONTENTS_LADDER) tempflag = 2; + } + if(!tempflag && ent->groundentity) + { + Get_RouteOrigin(zc->routeindex,v); + v[2] = ent->s.origin[2];//0; + rs_trace = gi.trace (ent->s.origin, trmin,ent->maxs, v,ent, MASK_BOTSOLID ); + if(rs_trace.contents & CONTENTS_LADDER) tempflag = 3; + } + + //right + if(!tempflag) + { + iyaw = zc->moveyaw + 90; + if(iyaw > 180) iyaw -= 360; + yaw = iyaw * M_PI * 2 / 360; + touchmin[0] = cos(yaw) * 32 ; + touchmin[1] = sin(yaw) * 32 ; + touchmin[2] = 0; + + VectorAdd(ent->s.origin,touchmin,touchmax); + rs_trace = gi.trace (ent->s.origin, trmin,ent->maxs, touchmax,ent, MASK_BOTSOLID ); + right = rs_trace.ent; + + if(rs_trace.contents & CONTENTS_LADDER) tempflag = true; + } + //left + if(!tempflag) + { + iyaw = zc->moveyaw - 90; + if(iyaw < -180) iyaw += 360; + yaw = iyaw * M_PI * 2 / 360; + touchmin[0] = cos(yaw) * 32 ; + touchmin[1] = sin(yaw) * 32 ; + touchmin[2] = 0; + + VectorAdd(ent->s.origin,touchmin,touchmax); + rs_trace = gi.trace (ent->s.origin, trmin,ent->maxs, touchmax,ent, MASK_BOTSOLID ); + left = rs_trace.ent; + + if(rs_trace.contents & CONTENTS_LADDER) tempflag = true; + } + + //ladder + if(tempflag) + { +#ifdef _DEBUG +gi.bprintf(PRINT_HIGH,"ladder founded! %f\n",iyaw); +#endif + VectorCopy(rs_trace.endpos,trmax); + VectorCopy(trmax,touchmax); + touchmax[2] += 8190; + rs_trace = gi.trace (/*temppos*/trmax, trmin,ent->maxs, touchmax,ent, MASK_SOLID ); + + e = rs_trace.ent; +//if((rs_trace.contents & CONTENTS_LADDER)) gi.bprintf(PRINT_HIGH,"damn!\n"); + + k = 0; + VectorCopy(rs_trace.endpos,temppos); + VectorAdd(rs_trace.endpos,touchmin,touchmax); + rs_trace = gi.trace (temppos, trmin,ent->maxs, touchmax,ent, MASK_BOTSOLID); + + if(e) + { + if(Q_stricmp (e->classname, "func_door") == 0) + { + k = true; + } + } + + if((!(rs_trace.contents & CONTENTS_LADDER) || k) /*&& rs_trace.fraction < 1.0*/) + { +// gi.WriteByte (svc_temp_entity); +// gi.WriteByte (TE_RAILTRAIL); +// gi.WritePosition (ent->s.origin); +// gi.WritePosition (temppos); +// gi.multicast (ent->s.origin, MULTICAST_PHS); + + ent->velocity[0] = 0; + ent->velocity[1] = 0; + if(zc->moveyaw == iyaw || zc->route_trace) + { +#ifdef _DEBUG +gi.bprintf(PRINT_HIGH,"ladder On!\n"); +#endif + +/* x = Get_yaw(rs_trace.plane.normal); + x += 180; + if(x > 180) x -= 360; + zc->moveyaw = x;*/ + if(zc->moveyaw != iyaw) zc->moveyaw = iyaw; + + ent->s.angles[YAW] = zc->moveyaw; + if(tempflag != 3) VectorCopy(trmax,ent->s.origin); + zc->zcstate |= STS_LADDERUP; + ent->s.angles[YAW] = zc->moveyaw; + ent->s.angles[PITCH] = -29; + + if(tempflag == 2) + { + ent->velocity[2] += VEL_BOT_JUMP; + gi.sound(ent, CHAN_VOICE, gi.soundindex("*jump1.wav"), 1, ATTN_NORM, 0); + PlayerNoise(ent, ent->s.origin, PNOISE_SELF); //pon + Set_BotAnim(ent,ANIM_JUMP,FRAME_jump1-1,FRAME_jump6); + zc->zcstate |= STS_SJMASK; +// ent->s.frame = FRAME_jump1-1; +// ent->client->anim_end = FRAME_jump6; +// ent->client->anim_priority = ANIM_JUMP; + ent->moveinfo.speed = 0; + } + else if(tempflag == 3) + { + ent->velocity[2] += VEL_BOT_JUMP; + gi.sound(ent, CHAN_VOICE, gi.soundindex("*jump1.wav"), 1, ATTN_NORM, 0); + PlayerNoise(ent, ent->s.origin, PNOISE_SELF); //pon + Set_BotAnim(ent,ANIM_JUMP,FRAME_jump1-1,FRAME_jump6); + zc->zcstate |= STS_SJMASK; +// ent->s.frame = FRAME_jump1-1; +// ent->client->anim_end = FRAME_jump6; +// ent->client->anim_priority = ANIM_JUMP; + ent->moveinfo.speed = MOVE_SPD_JUMP; + } + else if(ent->waterlevel > 1) + { + ent->velocity[2] = VEL_BOT_WLADRUP; +// if(VectorCompare(ent->s.origin,ent->s.old_origin)) ent->velocity[2] += 50; + } + else + { + ent->velocity[2] = VEL_BOT_LADRUP; +// if(VectorCompare(ent->s.origin,ent->s.old_origin)) ent->velocity[2] += 50; + } +// gi.bprintf(PRINT_HIGH,"ladder!!\n"); +// goto VCHCANSEL; + } + else + { + zc->moveyaw = iyaw; + ent->s.angles[YAW] = zc->moveyaw; + } +// goto VCHCANSEL; + } + } + } +VCHCANSEL_L: + //-------------------------------------------------------------------------------------- + //player sizebox set + // ducked + //special duckset + if(ent->client->zc.battleduckcnt > 0 && ent->groundentity && ent->velocity[2] < 10) + { + ent->client->ps.pmove.pm_flags |= PMF_DUCKED; + ent->client->zc.battleduckcnt--; + } + + if(ent->client->ps.pmove.pm_flags & PMF_DUCKED) + { + ent->client->zc.n_duckedtime = 0; + ent->maxs[2] = 4; + ent->viewheight = -2; + } + // not ducked + else + { + if(ent->client->zc.n_duckedtime < FRAMETIME * 10) ent->client->zc.n_duckedtime += FRAMETIME; + ent->maxs[2] = 32; + ent->viewheight = 22; + } + + //-------------------------------------------------------------------------------------- + // angle set + VectorCopy(ent->s.angles,ent->client->v_angle); + if(ent->s.angles[PITCH] < -29) ent->s.angles[PITCH] = -29; + else if(ent->s.angles[PITCH] > 29) ent->s.angles[PITCH] = 29; + + //-------------------------------------------------------------------------------------- + +//ZOID + if (ent->client->ctf_grapple) + { +/* if(ent->client->ctf_grapplestate > CTF_GRAPPLE_STATE_FLY) + { + if(ent->waterlevel && !waterjumped) VectorClear(ent->velocity); + }*/ + CTFGrapplePull(ent->client->ctf_grapple); + if(ent->client->ctf_grapplestate > CTF_GRAPPLE_STATE_FLY) + { + e = (edict_t*)ent->client->ctf_grapple; + //ent->velocity[2] = ent->gravity * sv_gravity->value * FRAMETIME; + if(ent->groundentity && ent->velocity[2] < 0) ent->velocity[2] = ent->gravity * sv_gravity->value * FRAMETIME; + else if(VectorCompare(ent->s.origin,ent->s.old_origin)) + { + ent->velocity[2] += JumpMax;//ent->gravity * sv_gravity->value * FRAMETIME * 4; + + if(ent->client->ctf_grapplestate == CTF_GRAPPLE_STATE_PULL) + { + VectorSubtract(ent->s.origin,e->s.origin,v); + yaw = Get_yaw(v); + + yaw = yaw * M_PI * 2 / 360; + ent->velocity[0] += cos(yaw) * 200 ; //start + ent->velocity[1] += sin(yaw) * 200 ; + } + } + else ent->velocity[2] += ent->gravity * sv_gravity->value * FRAMETIME * 2; + } + } + else + { + if(ent->waterlevel > 2) {ent->velocity[0] = 0;ent->velocity[1] = 0;/*VectorClear(ent->velocity);*/} + else if(ent->waterlevel && !ent->groundentity && ent->velocity[2] < 0) VectorClear(ent->velocity);//ent->velocity[2] = 0; + } +//ZOID + +/* +ent->velocity[0] = 800 * (random() - 0.5); +ent->velocity[1] = 800 * (random() - 0.5); + +ent->client->ps.pmove.pm_flags |= PMF_DUCKED; +*/ + ent->client->zc.trapped = false; //trapcatch clear + + gi.linkentity (ent); + G_TouchTriggers (ent); +} diff --git a/src/botstr.h b/src/botstr.h new file mode 100644 index 0000000..fb5ed8d --- /dev/null +++ b/src/botstr.h @@ -0,0 +1,98 @@ +#ifndef BOTSTRUCT +#define BOTSTRUCT + +//Zigock client info +#define ALEAT_MAX 10 + +typedef struct zgcl_s +{ + int zclass; //class no. + + int botindex; //botlist's index NO. + +// true client用 zoom フラグ + int aiming; //0-not 1-aiming 2-firing zoomingflag + float distance; //zoom中のFOV値 + float olddistance; //旧zooming FOV値 + qboolean autozoom; //autozoom + qboolean lockon; //lockon flag false-not true-locking + +// bot用 + int zcstate; //status + int zccmbstt; //combat status + + //duck + float n_duckedtime; //non ducked time + + //targets + edict_t *first_target; //enemy uses LockOntarget(for client) + float targetlock; //target locking time + short firstinterval; //enemy search count + edict_t *second_target; //kindof items + short secondinterval; //item pickup call count + + //waiting + vec3_t movtarget_pt; //moving target waiting point + edict_t *waitin_obj; //for waiting sequence complete + + //basical moving + float moveyaw; //true moving yaw + + //combat + int total_bomb; //total put bomb + float gren_time; //grenade time + + //contents +// int front_contents; + int ground_contents; + float ground_slope; + + //count (inc only) + int tmpcount; + + //moving hist + float nextcheck; //checking time + vec3_t pold_origin; //old origin + vec3_t pold_angles; //old angles + + //target object shot + qboolean objshot; + + + edict_t *sighten; //sighting enemy to me info from entity sight + edict_t *locked; //locking enemy to me info from lockon missile + + //waterstate + int waterstate; + + //route + qboolean route_trace; + int routeindex; //routing index + float rt_locktime; + float rt_releasetime; + qboolean havetarget; //target on/off + int targetindex; + + //battle + edict_t *last_target; //old enemy + vec3_t last_pos; //old origin + int battlemode; //mode + int battlecount; //temporary count + int battlesubcnt; //subcount + int battleduckcnt; //duck + float fbattlecount; //float temoporary count + vec3_t vtemp; //temporary vec + int foundedenemy; //foundedenemy + char secwep_selected;//secondweapon selected + + vec3_t aimedpos; //shottenpoint + qboolean trapped; //trapflag + + //team + short tmplstate; //teamplay state + short ctfstate; //ctf state + edict_t *followmate; //follow + float matelock; //team mate locking time +} zgcl_t; + +#endif diff --git a/src/g_chase.c b/src/g_chase.c new file mode 100644 index 0000000..b5d4d8a --- /dev/null +++ b/src/g_chase.c @@ -0,0 +1,155 @@ +#include "g_local.h" + +void UpdateChaseCam(edict_t *ent) +{ + vec3_t o, ownerv, goal; + edict_t *targ; + vec3_t forward, right; + trace_t trace; + int i; + vec3_t oldgoal; + vec3_t angles; + + // is our chase target gone? + if (!ent->client->chase_target->inuse + || ent->client->chase_target->client->resp.spectator) { + edict_t *old = ent->client->chase_target; + ChaseNext(ent); + if (ent->client->chase_target == old) { + ent->client->chase_target = NULL; + ent->client->ps.pmove.pm_flags &= ~PMF_NO_PREDICTION; + return; + } + } + + targ = ent->client->chase_target; + + VectorCopy(targ->s.origin, ownerv); + VectorCopy(ent->s.origin, oldgoal); + + ownerv[2] += targ->viewheight; + + VectorCopy(targ->client->v_angle, angles); + if (angles[PITCH] > 56) + angles[PITCH] = 56; + AngleVectors (angles, forward, right, NULL); + VectorNormalize(forward); + VectorMA(ownerv, -30, forward, o); + + if (o[2] < targ->s.origin[2] + 20) + o[2] = targ->s.origin[2] + 20; + + // jump animation lifts + if (!targ->groundentity) + o[2] += 16; + + trace = gi.trace(ownerv, vec3_origin, vec3_origin, o, targ, MASK_SOLID); + + VectorCopy(trace.endpos, goal); + + VectorMA(goal, 2, forward, goal); + + // pad for floors and ceilings + VectorCopy(goal, o); + o[2] += 6; + trace = gi.trace(goal, vec3_origin, vec3_origin, o, targ, MASK_SOLID); + if (trace.fraction < 1) { + VectorCopy(trace.endpos, goal); + goal[2] -= 6; + } + + VectorCopy(goal, o); + o[2] -= 6; + trace = gi.trace(goal, vec3_origin, vec3_origin, o, targ, MASK_SOLID); + if (trace.fraction < 1) { + VectorCopy(trace.endpos, goal); + goal[2] += 6; + } + + if (targ->deadflag) + ent->client->ps.pmove.pm_type = PM_DEAD; + else + ent->client->ps.pmove.pm_type = PM_FREEZE; + + VectorCopy(goal, ent->s.origin); + for (i=0 ; i<3 ; i++) + ent->client->ps.pmove.delta_angles[i] = ANGLE2SHORT(targ->client->v_angle[i] - ent->client->resp.cmd_angles[i]); + + if (targ->deadflag) { + ent->client->ps.viewangles[ROLL] = 40; + ent->client->ps.viewangles[PITCH] = -15; + ent->client->ps.viewangles[YAW] = targ->client->killer_yaw; + } else { + VectorCopy(targ->client->v_angle, ent->client->ps.viewangles); + VectorCopy(targ->client->v_angle, ent->client->v_angle); + } + + ent->viewheight = 0; + ent->client->ps.pmove.pm_flags |= PMF_NO_PREDICTION; + gi.linkentity(ent); +} + +void ChaseNext(edict_t *ent) +{ + int i; + edict_t *e; + + if (!ent->client->chase_target) + return; + + i = ent->client->chase_target - g_edicts; + do { + i++; + if (i > maxclients->value) + i = 1; + e = g_edicts + i; + if (!e->inuse) + continue; + if (!e->client->resp.spectator) + break; + } while (e != ent->client->chase_target); + + ent->client->chase_target = e; + ent->client->update_chase = true; +} + +void ChasePrev(edict_t *ent) +{ + int i; + edict_t *e; + + if (!ent->client->chase_target) + return; + + i = ent->client->chase_target - g_edicts; + do { + i--; + if (i < 1) + i = maxclients->value; + e = g_edicts + i; + if (!e->inuse) + continue; + if (!e->client->resp.spectator) + break; + } while (e != ent->client->chase_target); + + ent->client->chase_target = e; + ent->client->update_chase = true; +} + +void GetChaseTarget(edict_t *ent) +{ + int i; + edict_t *other; + + for (i = 1; i <= maxclients->value; i++) { + other = g_edicts + i; + if (other->inuse && !other->client->resp.spectator) { + ent->client->chase_target = other; + ent->client->update_chase = true; + UpdateChaseCam(ent); + return; + } + } + gi.centerprintf(ent, "No other players to chase."); +} \ No newline at end of file diff --git a/src/g_cmds.c b/src/g_cmds.c new file mode 100644 index 0000000..740b3d4 --- /dev/null +++ b/src/g_cmds.c @@ -0,0 +1,1168 @@ +#include "g_local.h" +#include "m_player.h" +#include "bot.h" + +char *ClientTeam (edict_t *ent) +{ + char *p; + static char value[512]; + + value[0] = 0; + + if (!ent->client) + return value; + + strcpy(value, Info_ValueForKey (ent->client->pers.userinfo, "skin")); + p = strchr(value, '/'); + if (!p) + return value; + + if ((int)(dmflags->value) & DF_MODELTEAMS) + { + *p = 0; + return value; + } + + // if ((int)(dmflags->value) & DF_SKINTEAMS) + return ++p; +} + +qboolean OnSameTeam (edict_t *ent1, edict_t *ent2) +{ + char ent1Team [512]; + char ent2Team [512]; + + if (!((int)(dmflags->value) & (DF_MODELTEAMS | DF_SKINTEAMS))) + return false; + + strcpy (ent1Team, ClientTeam (ent1)); + strcpy (ent2Team, ClientTeam (ent2)); + + if (strcmp(ent1Team, ent2Team) == 0) + return true; + return false; +} + + +void SelectNextItem (edict_t *ent, int itflags) +{ + gclient_t *cl; + int i, index; + gitem_t *it; + + cl = ent->client; + +//ZOID + if (cl->chase_target) { + ChaseNext(ent); + return; + } + +/* if (cl->menu) { + PMenu_Next(ent); + return; + } else if (cl->chase_target) { + ChaseNext(ent); + return; + }*/ +//ZOID + + // scan for the next valid one + for (i=1 ; i<=MAX_ITEMS ; i++) + { + index = (cl->pers.selected_item + i)%MAX_ITEMS; + if (!cl->pers.inventory[index]) + continue; + it = &itemlist[index]; + if (!it->use) + continue; + if (!(it->flags & itflags)) + continue; + + cl->pers.selected_item = index; + return; + } + + cl->pers.selected_item = -1; +} + +void SelectPrevItem (edict_t *ent, int itflags) +{ + gclient_t *cl; + int i, index; + gitem_t *it; + + cl = ent->client; + +//ZOID + if (cl->menu) { + PMenu_Prev(ent); + return; + } else if (cl->chase_target) { + ChasePrev(ent); + return; + } +//ZOID + + // scan for the next valid one + for (i=1 ; i<=MAX_ITEMS ; i++) + { + index = (cl->pers.selected_item + MAX_ITEMS - i)%MAX_ITEMS; + if (!cl->pers.inventory[index]) + continue; + it = &itemlist[index]; + if (!it->use) + continue; + if (!(it->flags & itflags)) + continue; + + cl->pers.selected_item = index; + return; + } + + cl->pers.selected_item = -1; +} + +void ValidateSelectedItem (edict_t *ent) +{ + gclient_t *cl; + + cl = ent->client; + + if (cl->pers.inventory[cl->pers.selected_item]) + return; // valid + + SelectNextItem (ent, -1); +} + + +//================================================================================= + +/* +================== +Cmd_Give_f + +Give items to a client +================== +*/ +void Cmd_Give_f (edict_t *ent) +{ + char *name; + gitem_t *it; + int index; + int i; + qboolean give_all; + edict_t *it_ent; + + if (deathmatch->value && !sv_cheats->value) + { + gi.cprintf (ent, PRINT_HIGH, "You must run the server with '+set cheats 1' to enable this command.\n"); + return; + } + + name = gi.args(); + + if (Q_stricmp(name, "all") == 0) + give_all = true; + else + give_all = false; + + if (give_all || Q_stricmp(gi.argv(1), "health") == 0) + { + if (gi.argc() == 3) + ent->health = atoi(gi.argv(2)); + else + ent->health = ent->max_health; + if (!give_all) + return; + } + + if (give_all || Q_stricmp(name, "weapons") == 0) + { + for (i=0 ; ipickup) + continue; + if (!(it->flags & IT_WEAPON)) + continue; + ent->client->pers.inventory[i] += 1; + } + if (!give_all) + return; + } + + if (give_all || Q_stricmp(name, "ammo") == 0) + { + for (i=0 ; ipickup) + continue; + if (!(it->flags & IT_AMMO)) + continue; + Add_Ammo (ent, it, 1000); + } + if (!give_all) + return; + } + + if (give_all || Q_stricmp(name, "armor") == 0) + { + gitem_armor_t *info; + + it = FindItem("Jacket Armor"); + ent->client->pers.inventory[ITEM_INDEX(it)] = 0; + + it = FindItem("Combat Armor"); + ent->client->pers.inventory[ITEM_INDEX(it)] = 0; + + it = FindItem("Body Armor"); + info = (gitem_armor_t *)it->info; + ent->client->pers.inventory[ITEM_INDEX(it)] = info->max_count; + + if (!give_all) + return; + } + + if (give_all || Q_stricmp(name, "Power Shield") == 0) + { + it = FindItem("Power Shield"); + it_ent = G_Spawn(); + it_ent->classname = it->classname; + SpawnItem (it_ent, it); + Touch_Item (it_ent, ent, NULL, NULL); + if (it_ent->inuse) + G_FreeEdict(it_ent); + + if (!give_all) + return; + } + + if (give_all) + { + for (i=0 ; ipickup) + continue; + if (it->flags & (IT_ARMOR|IT_WEAPON|IT_AMMO)) + continue; + ent->client->pers.inventory[i] = 1; + } + return; + } + + it = FindItem (name); + if (!it) + { + name = gi.argv(1); + it = FindItem (name); + if (!it) + { + gi.dprintf ("unknown item\n"); + return; + } + } + + if (!it->pickup) + { + gi.dprintf ("non-pickup item\n"); + return; + } + + index = ITEM_INDEX(it); + + if (it->flags & IT_AMMO) + { + if (gi.argc() == 3) + ent->client->pers.inventory[index] = atoi(gi.argv(2)); + else + ent->client->pers.inventory[index] += it->quantity; + } + else + { + it_ent = G_Spawn(); + it_ent->classname = it->classname; + SpawnItem (it_ent, it); + Touch_Item (it_ent, ent, NULL, NULL); + if (it_ent->inuse) + G_FreeEdict(it_ent); + } +} + + +/* +================== +Cmd_God_f + +Sets client to godmode + +argv(0) god +================== +*/ +void Cmd_God_f (edict_t *ent) +{ + char *msg; + + if (deathmatch->value && !sv_cheats->value) + { + gi.cprintf (ent, PRINT_HIGH, "You must run the server with '+set cheats 1' to enable this command.\n"); + return; + } + + ent->flags ^= FL_GODMODE; + if (!(ent->flags & FL_GODMODE) ) + msg = "godmode OFF\n"; + else + msg = "godmode ON\n"; + + gi.cprintf (ent, PRINT_HIGH, msg); +} + + +/* +================== +Cmd_Notarget_f + +Sets client to notarget + +argv(0) notarget +================== +*/ +void Cmd_Notarget_f (edict_t *ent) +{ + char *msg; + + if (deathmatch->value && !sv_cheats->value) + { + gi.cprintf (ent, PRINT_HIGH, "You must run the server with '+set cheats 1' to enable this command.\n"); + return; + } + + ent->flags ^= FL_NOTARGET; + if (!(ent->flags & FL_NOTARGET) ) + msg = "notarget OFF\n"; + else + msg = "notarget ON\n"; + + gi.cprintf (ent, PRINT_HIGH, msg); +} + + +/* +================== +Cmd_Noclip_f + +argv(0) noclip +================== +*/ +void Cmd_Noclip_f (edict_t *ent) +{ + char *msg; + + if (deathmatch->value && !sv_cheats->value) + { + gi.cprintf (ent, PRINT_HIGH, "You must run the server with '+set cheats 1' to enable this command.\n"); + return; + } + + if (ent->movetype == MOVETYPE_NOCLIP) + { + ent->movetype = MOVETYPE_WALK; + msg = "noclip OFF\n"; + } + else + { + ent->movetype = MOVETYPE_NOCLIP; + msg = "noclip ON\n"; + } + + gi.cprintf (ent, PRINT_HIGH, msg); +} + + +/* +================== +Cmd_Use_f + +Use an inventory item +================== +*/ +void Cmd_Use_f (edict_t *ent) +{ + int index; + gitem_t *it; + char *s; + + s = gi.args(); + it = FindItem (s); + if (!it) + { + gi.cprintf (ent, PRINT_HIGH, "unknown item: %s\n", s); + return; + } + if (!it->use) + { + gi.cprintf (ent, PRINT_HIGH, "Item is not usable.\n"); + return; + } + index = ITEM_INDEX(it); + if (!ent->client->pers.inventory[index]) + { + // RAFAEL + if (strcmp (it->pickup_name, "HyperBlaster") == 0) + { + it = Fdi_BOOMER;//FindItem ("Ionripper"); + index = ITEM_INDEX (it); + if (!ent->client->pers.inventory[index]) + { + gi.cprintf (ent, PRINT_HIGH, "Out of item: %s\n", s); + return; + } + } + // RAFAEL + else if (strcmp (it->pickup_name, "Railgun") == 0) + { + it = Fdi_PHALANX;//FindItem ("Phalanx"); + index = ITEM_INDEX (it); + if (!ent->client->pers.inventory[index]) + { + gi.cprintf (ent, PRINT_HIGH, "Out of item: %s\n", s); + return; + } + } + else + { + gi.cprintf (ent, PRINT_HIGH, "Out of item: %s\n", s); + return; + } + } + + it->use (ent, it); +} + + +/* +================== +Cmd_Drop_f + +Drop an inventory item +================== +*/ +void Cmd_Drop_f (edict_t *ent) +{ + int index; + gitem_t *it; + char *s; + +//ZOID--special case for tech powerups + if (Q_stricmp(gi.args(), "tech") == 0 && (it = CTFWhat_Tech(ent)) != NULL) { + it->drop (ent, it); + return; + } +//ZOID + + s = gi.args(); + it = FindItem (s); + if (!it) + { + gi.cprintf (ent, PRINT_HIGH, "unknown item: %s\n", s); + return; + } + if (!it->drop) + { + gi.cprintf (ent, PRINT_HIGH, "Item is not dropable.\n"); + return; + } + index = ITEM_INDEX(it); + if (!ent->client->pers.inventory[index]) + { + // RAFAEL + if (strcmp (it->pickup_name, "HyperBlaster") == 0) + { + it = Fdi_BOOMER;//FindItem ("Ionripper"); + index = ITEM_INDEX (it); + if (!ent->client->pers.inventory[index]) + { + gi.cprintf (ent, PRINT_HIGH, "Out of item: %s\n", s); + return; + } + } + // RAFAEL + else if (strcmp (it->pickup_name, "Railgun") == 0) + { + it = Fdi_PHALANX;//FindItem ("Phalanx"); + index = ITEM_INDEX (it); + if (!ent->client->pers.inventory[index]) + { + gi.cprintf (ent, PRINT_HIGH, "Out of item: %s\n", s); + return; + } + } + else + { + gi.cprintf (ent, PRINT_HIGH, "Out of item: %s\n", s); + return; + } + } + + it->drop (ent, it); +} + + +/* +================= +Cmd_Inven_f +================= +*/ +void Cmd_Inven_f (edict_t *ent) +{ + int i; + gclient_t *cl; + + if(ent->svflags & SVF_MONSTER) return; + + cl = ent->client; + + cl->showscores = false; + cl->showhelp = false; + +//ZOID + if (ent->client->menu) { + PMenu_Close(ent); + ent->client->update_chase = true; + return; + } +//ZOID + + if (cl->showinventory) + { + cl->showinventory = false; + return; + } + +//ZOID + if (ctf->value && cl->resp.ctf_team == CTF_NOTEAM) { + CTFOpenJoinMenu(ent); + return; + } +//ZOID + + cl->showinventory = true; + + gi.WriteByte (svc_inventory); + for (i=0 ; ipers.inventory[i]); + } + gi.unicast (ent, true); +} + +/* +================= +Cmd_InvUse_f +================= +*/ +void Cmd_InvUse_f (edict_t *ent) +{ + gitem_t *it; + +//ZOID + if (ent->client->menu) { + PMenu_Select(ent); + return; + } +//ZOID + + ValidateSelectedItem (ent); + + if (ent->client->pers.selected_item == -1) + { + gi.cprintf (ent, PRINT_HIGH, "No item to use.\n"); + return; + } + + it = &itemlist[ent->client->pers.selected_item]; + if (!it->use) + { + gi.cprintf (ent, PRINT_HIGH, "Item is not usable.\n"); + return; + } + it->use (ent, it); +} + +//ZOID +/* +================= +Cmd_LastWeap_f +================= +*/ +void Cmd_LastWeap_f (edict_t *ent) +{ + gclient_t *cl; + + cl = ent->client; + + if (!cl->pers.weapon || !cl->pers.lastweapon) + return; + + cl->pers.lastweapon->use (ent, cl->pers.lastweapon); +} +//ZOID + + +/* +================= +Cmd_WeapPrev_f +================= +*/ +void Cmd_WeapPrev_f (edict_t *ent) +{ + gclient_t *cl; + int i, index; + gitem_t *it; + int selected_weapon; + + cl = ent->client; + + if (!cl->pers.weapon) + return; + + selected_weapon = ITEM_INDEX(cl->pers.weapon); + + // scan for the next valid one + for (i=1 ; i<=MAX_ITEMS ; i++) + { + index = (selected_weapon + i)%MAX_ITEMS; + if (!cl->pers.inventory[index]) + continue; + it = &itemlist[index]; + if (!it->use) + continue; + if (! (it->flags & IT_WEAPON) ) + continue; + it->use (ent, it); + if (cl->pers.weapon == it) + return; // successful + } +} + +/* +================= +Cmd_WeapNext_f +================= +*/ +void Cmd_WeapNext_f (edict_t *ent) +{ + gclient_t *cl; + int i, index; + gitem_t *it; + int selected_weapon; + + cl = ent->client; + + if (!cl->pers.weapon) + return; + + selected_weapon = ITEM_INDEX(cl->pers.weapon); + + // scan for the next valid one + for (i=1 ; i<=MAX_ITEMS ; i++) + { + index = (selected_weapon + MAX_ITEMS - i)%MAX_ITEMS; + if (!cl->pers.inventory[index]) + continue; + it = &itemlist[index]; + if (!it->use) + continue; + if (! (it->flags & IT_WEAPON) ) + continue; + it->use (ent, it); + if (cl->pers.weapon == it) + return; // successful + } +} + +/* +================= +Cmd_WeapLast_f +================= +*/ +void Cmd_WeapLast_f (edict_t *ent) +{ + gclient_t *cl; + int index; + gitem_t *it; + + cl = ent->client; + + if (!cl->pers.weapon || !cl->pers.lastweapon) + return; + + index = ITEM_INDEX(cl->pers.lastweapon); + if (!cl->pers.inventory[index]) + return; + it = &itemlist[index]; + if (!it->use) + return; + if (! (it->flags & IT_WEAPON) ) + return; + it->use (ent, it); +} + +/* +================= +Cmd_InvDrop_f +================= +*/ +void Cmd_InvDrop_f (edict_t *ent) +{ + gitem_t *it; + + ValidateSelectedItem (ent); + + if (ent->client->pers.selected_item == -1) + { + gi.cprintf (ent, PRINT_HIGH, "No item to drop.\n"); + return; + } + + it = &itemlist[ent->client->pers.selected_item]; + if (!it->drop) + { + gi.cprintf (ent, PRINT_HIGH, "Item is not dropable.\n"); + return; + } + it->drop (ent, it); +} + +/* +================= +Cmd_Kill_f +================= +*/ +void Cmd_Kill_f (edict_t *ent) +{ +//ZOID + if (ent->solid == SOLID_NOT) + return; +//ZOID + + if((level.time - ent->client->respawn_time) < 5) + return; + ent->flags &= ~FL_GODMODE; + ent->health = 0; + meansOfDeath = MOD_SUICIDE; + player_die (ent, ent, ent, 100000, vec3_origin); + // don't even bother waiting for death frames + ent->deadflag = DEAD_DEAD; + respawn (ent); +} + +/* +================= +Cmd_PutAway_f +================= +*/ +void Cmd_PutAway_f (edict_t *ent) +{ + ent->client->showscores = false; + ent->client->showhelp = false; + ent->client->showinventory = false; +//ZOID + if (ent->client->menu) + PMenu_Close(ent); + ent->client->update_chase = true; +//ZOID +} + + +int PlayerSort (void const *a, void const *b) +{ + int anum, bnum; + + anum = *(int *)a; + bnum = *(int *)b; + + anum = game.clients[anum].ps.stats[STAT_FRAGS]; + bnum = game.clients[bnum].ps.stats[STAT_FRAGS]; + + if (anum < bnum) + return -1; + if (anum > bnum) + return 1; + return 0; +} + +/* +================= +Cmd_Players_f +================= +*/ +void Cmd_Players_f (edict_t *ent) +{ + int i; + int count; + char small[64]; + char large[1280]; + int index[256]; + + count = 0; + for (i = 0 ; i < maxclients->value ; i++) + if (game.clients[i].pers.connected) + { + index[count] = i; + count++; + } + + // sort by frags + qsort (index, count, sizeof(index[0]), PlayerSort); + + // print information + large[0] = 0; + + for (i = 0 ; i < count ; i++) + { + Com_sprintf (small, sizeof(small), "%3i %s\n", + game.clients[index[i]].ps.stats[STAT_FRAGS], + game.clients[index[i]].pers.netname); + if (strlen (small) + strlen(large) > sizeof(large) - 100 ) + { // can't print all of them in one packet + strcat (large, "...\n"); + break; + } + strcat (large, small); + } + + gi.cprintf (ent, PRINT_HIGH, "%s\n%i players\n", large, count); +} + +/* +================= +Cmd_Wave_f +================= +*/ +void Cmd_Wave_f (edict_t *ent) +{ + int i; + + i = atoi (gi.argv(1)); + + // can't wave when ducked + if (ent->client->ps.pmove.pm_flags & PMF_DUCKED) + return; + + if (ent->client->anim_priority > ANIM_WAVE) + return; + + ent->client->anim_priority = ANIM_WAVE; + + switch (i) + { + case 0: + gi.cprintf (ent, PRINT_HIGH, "flipoff\n"); + ent->s.frame = FRAME_flip01-1; + ent->client->anim_end = FRAME_flip12; + break; + case 1: + gi.cprintf (ent, PRINT_HIGH, "salute\n"); + ent->s.frame = FRAME_salute01-1; + ent->client->anim_end = FRAME_salute11; + break; + case 2: + gi.cprintf (ent, PRINT_HIGH, "taunt\n"); + ent->s.frame = FRAME_taunt01-1; + ent->client->anim_end = FRAME_taunt17; + break; + case 3: + gi.cprintf (ent, PRINT_HIGH, "wave\n"); + ent->s.frame = FRAME_wave01-1; + ent->client->anim_end = FRAME_wave11; + break; + case 4: + default: + gi.cprintf (ent, PRINT_HIGH, "point\n"); + ent->s.frame = FRAME_point01-1; + ent->client->anim_end = FRAME_point12; + break; + } +} + +/* +================== +Cmd_Say_f +================== +*/ +void Cmd_Say_f (edict_t *ent, qboolean team, qboolean arg0) +{ + int j; + edict_t *other; + char *p; + char text[2048]; + + if (gi.argc () < 2 && !arg0) + return; + + if (!((int)(dmflags->value) & (DF_MODELTEAMS | DF_SKINTEAMS))) + team = false; + + if (team) + Com_sprintf (text, sizeof(text), "(%s): ", ent->client->pers.netname); + else + Com_sprintf (text, sizeof(text), "%s: ", ent->client->pers.netname); + + if (arg0) + { + strcat (text, gi.argv(0)); + strcat (text, " "); + strcat (text, gi.args()); + } + else + { + p = gi.args(); + + if (*p == '"') + { + p++; + p[strlen(p)-1] = 0; + } + strcat(text, p); + } + + // don't let text be too long for malicious reasons + if (strlen(text) > 150) + text[150] = 0; + + strcat(text, "\n"); + + if (dedicated->value) + gi.cprintf(NULL, PRINT_CHAT, "%s", text); + + for (j = 1; j <= game.maxclients; j++) + { + other = &g_edicts[j]; + if (!other->inuse) + continue; + if (!other->client) + continue; + if (team) + { + if (!OnSameTeam(ent, other)) + continue; + } + if (other->svflags & SVF_MONSTER) continue; + + gi.cprintf(other, PRINT_CHAT, "%s", text); + } +} +//スナイパー用ZoomIn Out +void Cmd_ZoomIn(edict_t *ent) +{ + if( ent->client->zc.autozoom ) + { + gi.cprintf(ent,PRINT_HIGH,"autozoom has been selected.\n"); + return; + } + +// if( ent->client->pers.weapon != FindItem("Railgun")) return; + + if( ent->client->zc.aiming != 1 && ent->client->zc.aiming != 3) return; + + if(ent->client->zc.distance < 15 || ent->client->zc.distance > 90) + { + ent->client->zc.distance = 90; + ent->client->ps.fov = 90; + } + + if(ent->client->zc.distance > 15) + { + gi.sound (ent, CHAN_AUTO, gi.soundindex("3zb/zoom.wav"), 1, ATTN_NORM, 0); + if(ent->client->zc.distance == 90 ) ent->client->zc.distance = 65; + else if(ent->client->zc.distance == 65 ) ent->client->zc.distance = 40; + else ent->client->zc.distance = 15; + ent->client->ps.fov = ent->client->zc.distance; + } +} +void Cmd_ZoomOut(edict_t *ent) +{ + if( ent->client->zc.autozoom ) + { + gi.cprintf(ent,PRINT_HIGH,"autozoom has been selected.\n"); + return; + } + +// if( ent->client->pers.weapon != FindItem("Railgun")) return; + + if(ent->client->zc.aiming != 1 && ent->client->zc.aiming != 3) return; + + if(ent->client->zc.distance < 15 || ent->client->zc.distance > 90) + { + ent->client->zc.distance = 90; + ent->client->ps.fov = 90; + } + + if(ent->client->zc.distance < 90) + { + gi.sound (ent, CHAN_AUTO, gi.soundindex("3zb/zoom.wav"), 1, ATTN_NORM, 0); + if(ent->client->zc.distance == 15 ) ent->client->zc.distance = 40; + else if(ent->client->zc.distance == 40 ) ent->client->zc.distance = 65; + else ent->client->zc.distance = 90; + ent->client->ps.fov = ent->client->zc.distance; + } +} + +void Cmd_AutoZoom(edict_t *ent) +{ + if( ent->client->zc.autozoom ) + { + gi.cprintf(ent,PRINT_HIGH,"autozoom off.\n"); + ent->client->zc.autozoom = false; + } + else + { + gi.cprintf(ent,PRINT_HIGH,"autozoom on.\n"); + ent->client->zc.autozoom = true; + } +} + +//chain の undo +void UndoChain(edict_t *ent ,int step) +{ + int count,i; + trace_t rs_trace; + + if(step < 2) count = 2; + else count = step; + + if(chedit->value && !ent->deadflag && ent == &g_edicts[1]) + { + for(i = CurrentIndex - 1;i > 0 ;i--) + { + if(Route[i].state == GRS_NORMAL) + { + rs_trace = gi.trace(Route[i].Pt,ent->mins,ent->maxs,Route[i].Pt,ent,MASK_BOTSOLID); + + if(--count <= 0 && !rs_trace.allsolid && !rs_trace.startsolid) break; + } + } + + gi.cprintf(ent,PRINT_HIGH,"backed %i %i steps.\n",CurrentIndex - i,step); + CurrentIndex = i; + VectorCopy(Route[CurrentIndex].Pt,ent->s.origin); + VectorCopy(Route[CurrentIndex].Pt,ent->s.old_origin); + + memset(&Route[CurrentIndex],0,sizeof(route_t)); + if(CurrentIndex > 0) Route[CurrentIndex].index = Route[CurrentIndex - 1].index + 1; + } +} + +/* +================= +ClientCommand +================= +*/ +void ClientCommand (edict_t *ent) +{ + char *cmd; + + if (!ent->client) + return; // not fully in game yet + + cmd = gi.argv(0); + + if (Q_stricmp (cmd, "players") == 0) + { + Cmd_Players_f (ent); + return; + } + if (Q_stricmp (cmd, "say") == 0) + { + Cmd_Say_f (ent, false, false); + return; + } + if (Q_stricmp (cmd, "say_team") == 0) + { + Cmd_Say_f (ent, true, false); + return; + } + if (Q_stricmp (cmd, "score") == 0) + { + Cmd_Score_f (ent); + return; + } + if (Q_stricmp (cmd, "help") == 0) + { + Cmd_Help_f (ent); + return; + } + + if (level.intermissiontime) + return; + + if (Q_stricmp (cmd, "use") == 0) + Cmd_Use_f (ent); + else if (Q_stricmp (cmd, "drop") == 0) + Cmd_Drop_f (ent); + else if (Q_stricmp (cmd, "give") == 0) + Cmd_Give_f (ent); + else if (Q_stricmp (cmd, "god") == 0) + Cmd_God_f (ent); + else if (Q_stricmp (cmd, "notarget") == 0) + Cmd_Notarget_f (ent); + else if (Q_stricmp (cmd, "noclip") == 0) + Cmd_Noclip_f (ent); + else if (Q_stricmp (cmd, "inven") == 0) + Cmd_Inven_f (ent); + else if (Q_stricmp (cmd, "invnext") == 0) + SelectNextItem (ent, -1); + else if (Q_stricmp (cmd, "invprev") == 0) + SelectPrevItem (ent, -1); + else if (Q_stricmp (cmd, "invnextw") == 0) + SelectNextItem (ent, IT_WEAPON); + else if (Q_stricmp (cmd, "invprevw") == 0) + SelectPrevItem (ent, IT_WEAPON); + else if (Q_stricmp (cmd, "invnextp") == 0) + SelectNextItem (ent, IT_POWERUP); + else if (Q_stricmp (cmd, "invprevp") == 0) + SelectPrevItem (ent, IT_POWERUP); + else if (Q_stricmp (cmd, "invuse") == 0) + Cmd_InvUse_f (ent); + else if (Q_stricmp (cmd, "invdrop") == 0) + Cmd_InvDrop_f (ent); + else if (Q_stricmp (cmd, "weapprev") == 0) + Cmd_WeapPrev_f (ent); + else if (Q_stricmp (cmd, "weapnext") == 0) + Cmd_WeapNext_f (ent); + else if (Q_stricmp (cmd, "weaplast") == 0) + Cmd_WeapLast_f (ent); + else if (Q_stricmp (cmd, "kill") == 0) + Cmd_Kill_f (ent); + else if (Q_stricmp (cmd, "putaway") == 0) + Cmd_PutAway_f (ent); + else if (Q_stricmp (cmd, "wave") == 0) + Cmd_Wave_f (ent); + else if (Q_stricmp (cmd, "zoomin") == 0) //zoom + Cmd_ZoomIn(ent); + else if (Q_stricmp (cmd, "zoomout") == 0) + Cmd_ZoomOut(ent); + else if (Q_stricmp (cmd, "autozoom") == 0) + Cmd_AutoZoom(ent); + else if (Q_stricmp (cmd, "air") == 0) + Cmd_AirStrike(ent); + else if (Q_stricmp (cmd, "undo") == 0) + { + if(gi.argc() <= 1) UndoChain(ent,1); + else UndoChain (ent,atoi(gi.argv(1))); + } +//ZOID + else if (Q_stricmp (cmd, "team") == 0) + { + CTFTeam_f (ent); + } else if (Q_stricmp(cmd, "id") == 0) { + CTFID_f (ent); + } +//ZOID + else // anything that doesn't match a command will be a chat + Cmd_Say_f (ent, false, true); +} diff --git a/src/g_combat.c b/src/g_combat.c new file mode 100644 index 0000000..0932b1c --- /dev/null +++ b/src/g_combat.c @@ -0,0 +1,542 @@ +// g_combat.c + +#include "g_local.h" +#include "bot.h" +/* +============ +CanDamage + +Returns true if the inflictor can directly damage the target. Used for +explosions and melee attacks. +============ +*/ +qboolean CanDamage (edict_t *targ, edict_t *inflictor) +{ + vec3_t dest; + trace_t trace; + +// bmodels need special checking because their origin is 0,0,0 + if (targ->movetype == MOVETYPE_PUSH) + { + VectorAdd (targ->absmin, targ->absmax, dest); + VectorScale (dest, 0.5, dest); + trace = gi.trace (inflictor->s.origin, vec3_origin, vec3_origin, dest, inflictor, MASK_SOLID); + if (trace.fraction == 1.0) + return true; + if (trace.ent == targ) + return true; + return false; + } + + trace = gi.trace (inflictor->s.origin, vec3_origin, vec3_origin, targ->s.origin, inflictor, MASK_SOLID); + if (trace.fraction == 1.0) + return true; + + VectorCopy (targ->s.origin, dest); + dest[0] += 15.0; + dest[1] += 15.0; + trace = gi.trace (inflictor->s.origin, vec3_origin, vec3_origin, dest, inflictor, MASK_SOLID); + if (trace.fraction == 1.0) + return true; + + VectorCopy (targ->s.origin, dest); + dest[0] += 15.0; + dest[1] -= 15.0; + trace = gi.trace (inflictor->s.origin, vec3_origin, vec3_origin, dest, inflictor, MASK_SOLID); + if (trace.fraction == 1.0) + return true; + + VectorCopy (targ->s.origin, dest); + dest[0] -= 15.0; + dest[1] += 15.0; + trace = gi.trace (inflictor->s.origin, vec3_origin, vec3_origin, dest, inflictor, MASK_SOLID); + if (trace.fraction == 1.0) + return true; + + VectorCopy (targ->s.origin, dest); + dest[0] -= 15.0; + dest[1] -= 15.0; + trace = gi.trace (inflictor->s.origin, vec3_origin, vec3_origin, dest, inflictor, MASK_SOLID); + if (trace.fraction == 1.0) + return true; + + + return false; +} + + +/* +============ +Killed +============ +*/ +void Killed (edict_t *targ, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + if (targ->health < -999) + targ->health = -999; + + targ->enemy = attacker; + + if ((targ->svflags & SVF_MONSTER) && (targ->deadflag != DEAD_DEAD)) + { +// targ->svflags |= SVF_DEADMONSTER; // now treat as a different content type + if (!(targ->monsterinfo.aiflags & AI_GOOD_GUY)) + { + level.killed_monsters++; + if (coop->value && attacker->client) + attacker->client->resp.score++; + // medics won't heal monsters that they kill themselves + if (strcmp(attacker->classname, "monster_medic") == 0) + targ->owner = attacker; + } + } + + if (targ->movetype == MOVETYPE_PUSH || targ->movetype == MOVETYPE_STOP || targ->movetype == MOVETYPE_NONE) + { // doors, triggers, etc + targ->die (targ, inflictor, attacker, damage, point); + return; + } + +/* if ((targ->svflags & SVF_MONSTER) && (targ->deadflag != DEAD_DEAD)) + { + targ->touch = NULL; + monster_death_use (targ); + } +*/ + targ->die (targ, inflictor, attacker, damage, point); +} + + +/* +================ +SpawnDamage +================ +*/ +void SpawnDamage (int type, vec3_t origin, vec3_t normal, int damage) +{ + if (damage > 255) + damage = 255; + gi.WriteByte (svc_temp_entity); + gi.WriteByte (type); +// gi.WriteByte (damage); + gi.WritePosition (origin); + gi.WriteDir (normal); + gi.multicast (origin, MULTICAST_PVS); +} + + +/* +============ +T_Damage + +targ entity that is being damaged +inflictor entity that is causing the damage +attacker entity that caused the inflictor to damage targ + example: targ=monster, inflictor=rocket, attacker=player + +dir direction of the attack +point point at which the damage is being inflicted +normal normal vector from that point +damage amount of damage being inflicted +knockback force to be applied against targ as a result of the damage + +dflags these flags are used to control how T_Damage works + DAMAGE_RADIUS damage was indirect (from a nearby explosion) + DAMAGE_NO_ARMOR armor does not protect from this damage + DAMAGE_ENERGY damage is from an energy based weapon + DAMAGE_NO_KNOCKBACK do not affect velocity, just view angles + DAMAGE_BULLET damage is from a bullet (used for ricochets) + DAMAGE_NO_PROTECTION kills godmode, armor, everything +============ +*/ +static int CheckPowerArmor (edict_t *ent, vec3_t point, vec3_t normal, int damage, int dflags) +{ + gclient_t *client; + int save; + int power_armor_type; + int index; + int damagePerCell; + int pa_te_type; + int power; + int power_used; + + if (!damage) + return 0; + + client = ent->client; + + if (dflags & DAMAGE_NO_ARMOR) + return 0; + + if (client) + { + power_armor_type = PowerArmorType (ent); + if (power_armor_type != POWER_ARMOR_NONE) + { + index = ITEM_INDEX(Fdi_CELLS/*FindItem("Cells")*/); + power = client->pers.inventory[index]; + } + } + else if (ent->svflags & SVF_MONSTER) + { + power_armor_type = ent->monsterinfo.power_armor_type; + power = ent->monsterinfo.power_armor_power; + } + else + return 0; + + if (power_armor_type == POWER_ARMOR_NONE) + return 0; + if (!power) + return 0; + + if (power_armor_type == POWER_ARMOR_SCREEN) + { + vec3_t vec; + float dot; + vec3_t forward; + + // only works if damage point is in front + AngleVectors (ent->s.angles, forward, NULL, NULL); + VectorSubtract (point, ent->s.origin, vec); + VectorNormalize (vec); + dot = DotProduct (vec, forward); + if (dot <= 0.3) + return 0; + + damagePerCell = 1; + pa_te_type = TE_SCREEN_SPARKS; + damage = damage / 3; + } + else + { + damagePerCell = 2; + pa_te_type = TE_SHIELD_SPARKS; + damage = (2 * damage) / 3; + } + + save = power * damagePerCell; + if (!save) + return 0; + if (save > damage) + save = damage; + + SpawnDamage (pa_te_type, point, normal, save); + ent->powerarmor_time = level.time + 0.2; + + power_used = save / damagePerCell; + + if (client) + client->pers.inventory[index] -= power_used; + else + ent->monsterinfo.power_armor_power -= power_used; + return save; +} + +static int CheckArmor (edict_t *ent, vec3_t point, vec3_t normal, int damage, int te_sparks, int dflags) +{ + gclient_t *client; + int save; + int index; + gitem_t *armor; + + if (!damage) + return 0; + + client = ent->client; + + if (!client) + return 0; + + if (dflags & DAMAGE_NO_ARMOR) + return 0; + + index = ArmorIndex (ent); + if (!index) + return 0; + + armor = GetItemByIndex (index); + + if (dflags & DAMAGE_ENERGY) + save = ceil(((gitem_armor_t *)armor->info)->energy_protection*damage); + else + save = ceil(((gitem_armor_t *)armor->info)->normal_protection*damage); + if (save >= client->pers.inventory[index]) + save = client->pers.inventory[index]; + + if (!save) + return 0; + + client->pers.inventory[index] -= save; + SpawnDamage (te_sparks, point, normal, save); + + return save; +} + +qboolean CheckTeamDamage (edict_t *targ, edict_t *attacker) +{ +//ZOID + if (ctf->value && targ->client && attacker->client) + if (targ->client->resp.ctf_team == attacker->client->resp.ctf_team && + targ != attacker) + return true; +//ZOID + + //FIXME make the next line real and uncomment this block + // if ((ability to damage a teammate == OFF) && (targ's team == attacker's team)) + return false; +} + +void T_Damage (edict_t *targ, edict_t *inflictor, edict_t *attacker, vec3_t dir, vec3_t point, vec3_t normal, int damage, int knockback, int dflags, int mod) +{ + gclient_t *client; + int take; + int save; + int asave; + int psave; + int te_sparks; + + if (!targ->takedamage) + return; + + if(mod == MOD_CRUSH) + { + //bot's state change + if((targ->svflags & SVF_MONSTER) && targ->client) + { + if((targ->client->zc.waitin_obj == inflictor && targ->client->zc.zcstate) + || targ->groundentity == inflictor) + { +// gi.bprintf(PRINT_HIGH,"MOOOOOOOOOOOOOOOOOOOO\n"); + targ->client->zc.zcstate |= STS_W_DONT; + } + } + } + + // friendly fire avoidance + // if enabled you can't hurt teammates (but you can hurt yourself) + // knockback still occurs + if ((targ != attacker) && ((deathmatch->value && ((int)(dmflags->value) & (DF_MODELTEAMS | DF_SKINTEAMS))) || coop->value)) + { + if (OnSameTeam (targ, attacker)) + { + if ((int)(dmflags->value) & DF_NO_FRIENDLY_FIRE) + damage = 0; + else + mod |= MOD_FRIENDLY_FIRE; + } + else if(targ->client && !(targ->svflags & SVF_MONSTER)) + { + if(attacker->client) targ->client->zc.first_target = attacker; + } + } + meansOfDeath = mod; + + // easy mode takes half damage + if (skill->value == 0 && deathmatch->value == 0 && targ->client) + { + damage *= 0.5; + if (!damage) + damage = 1; + } + + client = targ->client; + + if (dflags & DAMAGE_BULLET) + te_sparks = TE_BULLET_SPARKS; + else + te_sparks = TE_SPARKS; + + VectorNormalize(dir); + +// bonus damage for suprising a monster + if (!(dflags & DAMAGE_RADIUS) && (targ->svflags & SVF_MONSTER) && (attacker->client) && (!targ->enemy) && (targ->health > 0)) + damage *= 2; + +//ZOID +//strength tech + damage = CTFApplyStrength(attacker, damage); +//ZOID + + if (targ->flags & FL_NO_KNOCKBACK) + knockback = 0; + +// figure momentum add + if (!(dflags & DAMAGE_NO_KNOCKBACK)) + { + if ((knockback) && (targ->movetype != MOVETYPE_NONE) && (targ->movetype != MOVETYPE_BOUNCE) && (targ->movetype != MOVETYPE_PUSH) && (targ->movetype != MOVETYPE_STOP)) + { + vec3_t kvel; + float mass; + + if (targ->mass < 50) + mass = 50; + else + mass = targ->mass; + + if (targ->client && attacker == targ) + VectorScale (dir, 1600.0 * (float)knockback / mass, kvel); // the rocket jump hack... + else + VectorScale (dir, 500.0 * (float)knockback / mass, kvel); + + VectorAdd (targ->velocity, kvel, targ->velocity); + } + } + + take = damage; + save = 0; + + // check for godmode + if ( (targ->flags & FL_GODMODE) && !(dflags & DAMAGE_NO_PROTECTION) ) + { + take = 0; + save = damage; + SpawnDamage (te_sparks, point, normal, save); + } + + // check for invincibility + if ((client && client->invincible_framenum > level.framenum ) && !(dflags & DAMAGE_NO_PROTECTION)) + { + if (targ->pain_debounce_time < level.time) + { + gi.sound(targ, CHAN_ITEM, gi.soundindex("items/protect3.wav"), 1, ATTN_NORM, 0); +// gi.sound(targ, CHAN_ITEM, gi.soundindex("items/protect4.wav"), 1, ATTN_NORM, 0); + targ->pain_debounce_time = level.time + 2; + } + take = 0; + save = damage; + } + +//ZOID +//team armor protect + if (ctf->value && targ->client && attacker->client && + targ->client->resp.ctf_team == attacker->client->resp.ctf_team && + targ != attacker && ((int)dmflags->value & DF_ARMOR_PROTECT)) { + psave = asave = 0; + } else { +//ZOID + + psave = CheckPowerArmor (targ, point, normal, take, dflags); + take -= psave; + + asave = CheckArmor (targ, point, normal, take, te_sparks, dflags); + take -= asave; + } + //treat cheat/powerup savings the same as armor + asave += save; + +//ZOID +//resistance tech + take = CTFApplyResistance(targ, take); +//ZOID + + // team damage avoidance + if (!(dflags & DAMAGE_NO_PROTECTION) && CheckTeamDamage (targ, attacker)) + return; + +//ZOID + CTFCheckHurtCarrier(targ, attacker); +//ZOID + +// do the damage + if (take) + { + if ((targ->svflags & SVF_MONSTER) || (client)) + { + SpawnDamage (TE_BLOOD, point, normal, take); + if(client && (targ->svflags & SVF_MONSTER) && attacker) + { + if(client->zc.battlemode & FIRE_CHIKEN) client->zc.battlemode &= ~FIRE_CHIKEN; + + if(mod == MOD_RAILGUN + || mod == MOD_BFG_LASER + || mod == MOD_ROCKET + || mod == MOD_BLASTER + || mod == MOD_RIPPER + || mod == MOD_HYPERBLASTER + || mod == MOD_PHALANX) + { + if(attacker->client + && (9 * random() < Bot[client->zc.botindex].param[BOP_REACTION]) + && !client->zc.first_target) + { + if(!OnSameTeam (targ, attacker)) client->zc.first_target = attacker; + } + } + } + } + else + SpawnDamage (te_sparks, point, normal, take); + + + targ->health = targ->health - take; + + if (targ->health <= 0) + { + if ((targ->svflags & SVF_MONSTER) || (client)) + targ->flags |= FL_NO_KNOCKBACK; + Killed (targ, inflictor, attacker, take, point); + return; + } + } + + if (client) + { + if (!(targ->flags & FL_GODMODE) && (take)) + targ->pain (targ, attacker, knockback, take); + } + else if (take) + { + if (targ->pain) + targ->pain (targ, attacker, knockback, take); + } + + // add to the damage inflicted on a player this frame + // the total will be turned into screen blends and view angle kicks + // at the end of the frame + if (client) + { + client->damage_parmor += psave; + client->damage_armor += asave; + client->damage_blood += take; + client->damage_knockback += knockback; + VectorCopy (point, client->damage_from); + } +} + + +/* +============ +T_RadiusDamage +============ +*/ +void T_RadiusDamage (edict_t *inflictor, edict_t *attacker, float damage, edict_t *ignore, float radius, int mod) +{ + float points; + edict_t *ent = NULL; + vec3_t v; + vec3_t dir; + + while ((ent = findradius(ent, inflictor->s.origin, radius)) != NULL) + { + if (ent == ignore) + continue; + if (!ent->takedamage) + continue; + + VectorAdd (ent->mins, ent->maxs, v); + VectorMA (ent->s.origin, 0.5, v, v); + VectorSubtract (inflictor->s.origin, v, v); + points = damage - 0.5 * VectorLength (v); + if (ent == attacker) + points = points * 0.5; + if (points > 0) + { + if (CanDamage (ent, inflictor)) + { + VectorSubtract (ent->s.origin, inflictor->s.origin, dir); + T_Damage (ent, inflictor, attacker, dir, inflictor->s.origin, vec3_origin, (int)points, (int)points, DAMAGE_RADIUS, mod); + } + } + } +} diff --git a/src/g_ctf.c b/src/g_ctf.c new file mode 100644 index 0000000..c7484b3 --- /dev/null +++ b/src/g_ctf.c @@ -0,0 +1,3227 @@ +#include "g_local.h" +#include "bot.h" + +typedef struct ctfgame_s +{ + int team1, team2; + int total1, total2; // these are only set when going into intermission! + float last_flag_capture; + int last_capture_team; +} ctfgame_t; +//PON +qboolean bots_moveok ( edict_t *ent,float ryaw,vec3_t pos,float dist,float *bottom); +//PON +ctfgame_t ctfgame; +qboolean techspawn = false; + +cvar_t *ctf; +cvar_t *ctf_forcejoin; + +char *ctf_statusbar = +"yb -24 " + +// health +"xv 0 " +"hnum " +"xv 50 " +"pic 0 " + +// ammo +"if 2 " +" xv 100 " +" anum " +" xv 150 " +" pic 2 " +"endif " + +// armor +"if 4 " +" xv 200 " +" rnum " +" xv 250 " +" pic 4 " +"endif " + +// selected item +"if 6 " +" xv 296 " +" pic 6 " +"endif " + +"yb -50 " + +// picked up item +"if 7 " +" xv 0 " +" pic 7 " +" xv 26 " +" yb -42 " +" stat_string 8 " +" yb -50 " +"endif " + +// timer +"if 9 " + "xv 246 " + "num 2 10 " + "xv 296 " + "pic 9 " +"endif " + +// help / weapon icon +"if 11 " + "xv 148 " + "pic 11 " +"endif " + +// frags +"xr -50 " +"yt 2 " +"num 3 14 " + +//tech +"yb -129 " +"if 26 " + "xr -26 " + "pic 26 " +"endif " + +// red team +"yb -102 " +"if 17 " + "xr -26 " + "pic 17 " +"endif " +"xr -62 " +"num 2 18 " +//joined overlay +"if 22 " + "yb -104 " + "xr -28 " + "pic 22 " +"endif " + +// blue team +"yb -75 " +"if 19 " + "xr -26 " + "pic 19 " +"endif " +"xr -62 " +"num 2 20 " +"if 23 " + "yb -77 " + "xr -28 " + "pic 23 " +"endif " + +// have flag graph +"if 21 " + "yt 26 " + "xr -24 " + "pic 21 " +"endif " + +// id view state +"if 27 " + "xv 0 " + "yb -58 " + "string \"Viewing\" " + "xv 64 " + "stat_string 27 " +"endif " + +//sight +"if 31 " +" xv 96 " +" yv 56 " +" pic 31 " +"endif" +; + +static char *tnames[] = { + "item_tech1", "item_tech2", "item_tech3", "item_tech4", + NULL +}; + +void stuffcmd(edict_t *ent, char *s) +{ + if(ent->svflags & SVF_MONSTER) return; + + gi.WriteByte (11); + gi.WriteString (s); + gi.unicast (ent, true); +} + +/*--------------------------------------------------------------------------*/ + +/* +================= +findradius + +Returns entities that have origins within a spherical area + +findradius (origin, radius) +================= +*/ +static edict_t *loc_findradius (edict_t *from, vec3_t org, float rad) +{ + vec3_t eorg; + int j; + + if (!from) + from = g_edicts; + else + from++; + for ( ; from < &g_edicts[globals.num_edicts]; from++) + { + if (!from->inuse) + continue; +#if 0 + if (from->solid == SOLID_NOT) + continue; +#endif + for (j=0 ; j<3 ; j++) + eorg[j] = org[j] - (from->s.origin[j] + (from->mins[j] + from->maxs[j])*0.5); + if (VectorLength(eorg) > rad) + continue; + return from; + } + + return NULL; +} + +static void loc_buildboxpoints(vec3_t p[8], vec3_t org, vec3_t mins, vec3_t maxs) +{ + VectorAdd(org, mins, p[0]); + VectorCopy(p[0], p[1]); + p[1][0] -= mins[0]; + VectorCopy(p[0], p[2]); + p[2][1] -= mins[1]; + VectorCopy(p[0], p[3]); + p[3][0] -= mins[0]; + p[3][1] -= mins[1]; + VectorAdd(org, maxs, p[4]); + VectorCopy(p[4], p[5]); + p[5][0] -= maxs[0]; + VectorCopy(p[0], p[6]); + p[6][1] -= maxs[1]; + VectorCopy(p[0], p[7]); + p[7][0] -= maxs[0]; + p[7][1] -= maxs[1]; +} + +static qboolean loc_CanSee (edict_t *targ, edict_t *inflictor) +{ + trace_t trace; + vec3_t targpoints[8]; + int i; + vec3_t viewpoint; + +// bmodels need special checking because their origin is 0,0,0 + if (targ->movetype == MOVETYPE_PUSH) + return false; // bmodels not supported + + loc_buildboxpoints(targpoints, targ->s.origin, targ->mins, targ->maxs); + + VectorCopy(inflictor->s.origin, viewpoint); + viewpoint[2] += inflictor->viewheight; + + for (i = 0; i < 8; i++) { + trace = gi.trace (viewpoint, vec3_origin, vec3_origin, targpoints[i], inflictor, MASK_SOLID); + if (trace.fraction == 1.0) + return true; + } + + return false; +} + +/*--------------------------------------------------------------------------*/ + +static gitem_t *flag1_item; +static gitem_t *flag2_item; + +void CTFInit(void) +{ + ctf = gi.cvar("ctf", "0", CVAR_SERVERINFO); + ctf_forcejoin = gi.cvar("ctf_forcejoin", "", 0); + + if (!flag1_item) + flag1_item = FindItemByClassname("item_flag_team1"); + if (!flag2_item) + flag2_item = FindItemByClassname("item_flag_team2"); + memset(&ctfgame, 0, sizeof(ctfgame)); + techspawn = false; +} + +/*--------------------------------------------------------------------------*/ + +char *CTFTeamName(int team) +{ + switch (team) { + case CTF_TEAM1: + return "RED"; + case CTF_TEAM2: + return "BLUE"; + } + return "UKNOWN"; +} + +char *CTFOtherTeamName(int team) +{ + switch (team) { + case CTF_TEAM1: + return "BLUE"; + case CTF_TEAM2: + return "RED"; + } + return "UKNOWN"; +} + +int CTFOtherTeam(int team) +{ + switch (team) { + case CTF_TEAM1: + return CTF_TEAM2; + case CTF_TEAM2: + return CTF_TEAM1; + } + return -1; // invalid value +} + +/*--------------------------------------------------------------------------*/ + +edict_t *SelectRandomDeathmatchSpawnPoint (void); +edict_t *SelectFarthestDeathmatchSpawnPoint (void); +float PlayersRangeFromSpot (edict_t *spot); + +void CTFAssignSkin(edict_t *ent, char *s) +{ + int playernum = ent-g_edicts-1; + char *p; + char t[64]; + + Com_sprintf(t, sizeof(t), "%s", s); + + if ((p = strrchr(t, '/')) != NULL) + p[1] = 0; + else + strcpy(t, "male/"); + + switch (ent->client->resp.ctf_team) { + case CTF_TEAM1: + gi.configstring (CS_PLAYERSKINS+playernum, va("%s\\%s%s", + ent->client->pers.netname, t, CTF_TEAM1_SKIN) ); + break; + case CTF_TEAM2: + gi.configstring (CS_PLAYERSKINS+playernum, + va("%s\\%s%s", ent->client->pers.netname, t, CTF_TEAM2_SKIN) ); + break; + default: + gi.configstring (CS_PLAYERSKINS+playernum, + va("%s\\%s", ent->client->pers.netname, s) ); + break; + } +// gi.cprintf(ent, PRINT_HIGH, "You have been assigned to %s team.\n", ent->client->pers.netname); +} + +void CTFAssignTeam(gclient_t *who) +{ + edict_t *player; + int i; + int team1count = 0, team2count = 0; + + who->resp.ctf_state = CTF_STATE_START; + + if (!((int)dmflags->value & DF_CTF_FORCEJOIN)) { + who->resp.ctf_team = CTF_NOTEAM; + return; + } + + for (i = 1; i <= maxclients->value; i++) { + player = &g_edicts[i]; + + if (!player->inuse || player->client == who) + continue; + + switch (player->client->resp.ctf_team) { + case CTF_TEAM1: + team1count++; + break; + case CTF_TEAM2: + team2count++; + } + } + if (team1count < team1count) + who->resp.ctf_team = CTF_TEAM1; + else if (team2count < team1count) + who->resp.ctf_team = CTF_TEAM2; + else if (rand() & 1) + who->resp.ctf_team = CTF_TEAM1; + else + who->resp.ctf_team = CTF_TEAM2; +} + +/* +================ +SelectCTFSpawnPoint + +go to a ctf point, but NOT the two points closest +to other players +================ +*/ +edict_t *SelectCTFSpawnPoint (edict_t *ent) +{ + edict_t *spot, *spot1, *spot2; + int count = 0; + int selection; + float range, range1, range2; + char *cname; + + if (ent->client->resp.ctf_state != CTF_STATE_START) + if ( (int)(dmflags->value) & DF_SPAWN_FARTHEST) + return SelectFarthestDeathmatchSpawnPoint (); + else + return SelectRandomDeathmatchSpawnPoint (); + + ent->client->resp.ctf_state = CTF_STATE_PLAYING; + + switch (ent->client->resp.ctf_team) { + case CTF_TEAM1: + cname = "info_player_team1"; + break; + case CTF_TEAM2: + cname = "info_player_team2"; + break; + default: + return SelectRandomDeathmatchSpawnPoint(); + } + + spot = NULL; + range1 = range2 = 99999; + spot1 = spot2 = NULL; + + while ((spot = G_Find (spot, FOFS(classname), cname)) != NULL) + { + count++; + range = PlayersRangeFromSpot(spot); + if (range < range1) + { + range1 = range; + spot1 = spot; + } + else if (range < range2) + { + range2 = range; + spot2 = spot; + } + } + + if (!count) + return SelectRandomDeathmatchSpawnPoint(); + + if (count <= 2) + { + spot1 = spot2 = NULL; + } + else + count -= 2; + + selection = rand() % count; + + spot = NULL; + do + { + spot = G_Find (spot, FOFS(classname), cname); + if (spot == spot1 || spot == spot2) + selection++; + } while(selection--); + + return spot; +} + +/*------------------------------------------------------------------------*/ +/* +CTFFragBonuses + +Calculate the bonuses for flag defense, flag carrier defense, etc. +Note that bonuses are not cumaltive. You get one, they are in importance +order. +*/ +void CTFFragBonuses(edict_t *targ, edict_t *inflictor, edict_t *attacker) +{ + int i; + edict_t *ent; + gitem_t *flag_item, *enemy_flag_item; + int otherteam; + edict_t *flag, *carrier; + char *c; + vec3_t v1, v2; + + // no bonus for fragging yourself + if (!targ->client || !attacker->client || targ == attacker) + return; + + otherteam = CTFOtherTeam(targ->client->resp.ctf_team); + if (otherteam < 0) + return; // whoever died isn't on a team + + // same team, if the flag at base, check to he has the enemy flag + if (targ->client->resp.ctf_team == CTF_TEAM1) { + flag_item = flag1_item; + enemy_flag_item = flag2_item; + } else { + flag_item = flag2_item; + enemy_flag_item = flag1_item; + } + + // did the attacker frag the flag carrier? + if (targ->client->pers.inventory[ITEM_INDEX(enemy_flag_item)]) { + attacker->client->resp.ctf_lastfraggedcarrier = level.time; + attacker->client->resp.score += CTF_FRAG_CARRIER_BONUS; + if(!(attacker->svflags & SVF_MONSTER)) + gi.cprintf(attacker, PRINT_MEDIUM, "BONUS: %d points for fragging enemy flag carrier.\n", + CTF_FRAG_CARRIER_BONUS); + + // the the target had the flag, clear the hurt carrier + // field on the other team + for (i = 1; i <= maxclients->value; i++) { + ent = g_edicts + i; + if (ent->inuse && ent->client->resp.ctf_team == otherteam) + ent->client->resp.ctf_lasthurtcarrier = 0; + } + return; + } + + if (targ->client->resp.ctf_lasthurtcarrier && + level.time - targ->client->resp.ctf_lasthurtcarrier < CTF_CARRIER_DANGER_PROTECT_TIMEOUT && + !attacker->client->pers.inventory[ITEM_INDEX(flag_item)]) { + // attacker is on the same team as the flag carrier and + // fragged a guy who hurt our flag carrier + attacker->client->resp.score += CTF_CARRIER_DANGER_PROTECT_BONUS; + gi.bprintf(PRINT_MEDIUM, "%s defends %s's flag carrier against an agressive enemy\n", + attacker->client->pers.netname, + CTFTeamName(attacker->client->resp.ctf_team)); + return; + } + + // flag and flag carrier area defense bonuses + + // we have to find the flag and carrier entities + + // find the flag + switch (attacker->client->resp.ctf_team) { + case CTF_TEAM1: + c = "item_flag_team1"; + break; + case CTF_TEAM2: + c = "item_flag_team2"; + break; + default: + return; + } + + flag = NULL; + while ((flag = G_Find (flag, FOFS(classname), c)) != NULL) { + if (!(flag->spawnflags & DROPPED_ITEM)) + break; + } + + if (!flag) + return; // can't find attacker's flag + +//PONKO + if(attacker) + { + VectorSubtract(targ->s.origin,attacker->s.origin,v1); + if(VectorLength(v1) < 300 + && attacker->client && !(attacker->deadflag) && (attacker->svflags & SVF_MONSTER)) + { + attacker->client->zc.second_target = flag; + } + } +//PONKO + // find attacker's team's flag carrier + for (i = 1; i <= maxclients->value; i++) { + carrier = g_edicts + i; + if (carrier->inuse && + carrier->client->pers.inventory[ITEM_INDEX(flag_item)]) + break; + carrier = NULL; + } + + // ok we have the attackers flag and a pointer to the carrier + + // check to see if we are defending the base's flag + VectorSubtract(targ->s.origin, flag->s.origin, v1); + VectorSubtract(attacker->s.origin, flag->s.origin, v2); + + if (VectorLength(v1) < CTF_TARGET_PROTECT_RADIUS || + VectorLength(v2) < CTF_TARGET_PROTECT_RADIUS || + loc_CanSee(flag, targ) || loc_CanSee(flag, attacker)) { + // we defended the base flag + attacker->client->resp.score += CTF_FLAG_DEFENSE_BONUS; + if (flag->solid == SOLID_NOT) + gi.bprintf(PRINT_MEDIUM, "%s defends the %s base.\n", + attacker->client->pers.netname, + CTFTeamName(attacker->client->resp.ctf_team)); + else + gi.bprintf(PRINT_MEDIUM, "%s defends the %s flag.\n", + attacker->client->pers.netname, + CTFTeamName(attacker->client->resp.ctf_team)); + return; + } + + if (carrier && carrier != attacker) { + VectorSubtract(targ->s.origin, carrier->s.origin, v1); + VectorSubtract(attacker->s.origin, carrier->s.origin, v1); + + if (VectorLength(v1) < CTF_ATTACKER_PROTECT_RADIUS || + VectorLength(v2) < CTF_ATTACKER_PROTECT_RADIUS || + loc_CanSee(carrier, targ) || loc_CanSee(carrier, attacker)) { + attacker->client->resp.score += CTF_CARRIER_PROTECT_BONUS; + gi.bprintf(PRINT_MEDIUM, "%s defends the %s's flag carrier.\n", + attacker->client->pers.netname, + CTFTeamName(attacker->client->resp.ctf_team)); + return; + } + } +} + +void CTFCheckHurtCarrier(edict_t *targ, edict_t *attacker) +{ + gitem_t *flag_item; + + if (!targ->client || !attacker->client) + return; + + if (targ->client->resp.ctf_team == CTF_TEAM1) + flag_item = flag2_item; + else + flag_item = flag1_item; + + if (targ->client->pers.inventory[ITEM_INDEX(flag_item)] && + targ->client->resp.ctf_team != attacker->client->resp.ctf_team) + attacker->client->resp.ctf_lasthurtcarrier = level.time; +} + + +/*------------------------------------------------------------------------*/ + +void CTFResetFlag(int ctf_team) +{ + char *c; + edict_t *ent; + + switch (ctf_team) { + case CTF_TEAM1: + c = "item_flag_team1"; + break; + case CTF_TEAM2: + c = "item_flag_team2"; + break; + default: + return; + } + + ent = NULL; + while ((ent = G_Find (ent, FOFS(classname), c)) != NULL) { + if (ent->spawnflags & DROPPED_ITEM) + G_FreeEdict(ent); + else { + ent->svflags &= ~SVF_NOCLIENT; + ent->solid = SOLID_TRIGGER; + gi.linkentity(ent); + ent->s.event = EV_ITEM_RESPAWN; + } + } +} + +void CTFResetFlags(void) +{ + CTFResetFlag(CTF_TEAM1); + CTFResetFlag(CTF_TEAM2); +} + +qboolean CTFPickup_Flag(edict_t *ent, edict_t *other) +{ + int ctf_team; + int i; + edict_t *player; + gitem_t *flag_item, *enemy_flag_item; + + + if(chedit->value) {SetRespawn (ent, 30); return true;}; + + + // figure out what team this flag is + if (strcmp(ent->classname, "item_flag_team1") == 0) + ctf_team = CTF_TEAM1; + else if (strcmp(ent->classname, "item_flag_team2") == 0) + ctf_team = CTF_TEAM2; + else { + if(!(ent->svflags & SVF_MONSTER)) + gi.cprintf(ent, PRINT_HIGH, "Don't know what team the flag is on.\n"); + return false; + } + + // same team, if the flag at base, check to he has the enemy flag + if (ctf_team == CTF_TEAM1) { + flag_item = flag1_item; + enemy_flag_item = flag2_item; + } else { + flag_item = flag2_item; + enemy_flag_item = flag1_item; + } + + if (ctf_team == other->client->resp.ctf_team) { + + if (!(ent->spawnflags & DROPPED_ITEM)) { + // the flag is at home base. if the player has the enemy + // flag, he's just won! + + if (other->client->pers.inventory[ITEM_INDEX(enemy_flag_item)]) { + gi.bprintf(PRINT_HIGH, "%s captured the %s flag!\n", + other->client->pers.netname, CTFOtherTeamName(ctf_team)); + other->client->pers.inventory[ITEM_INDEX(enemy_flag_item)] = 0; + + ctfgame.last_flag_capture = level.time; + ctfgame.last_capture_team = ctf_team; + if (ctf_team == CTF_TEAM1) + ctfgame.team1++; + else + ctfgame.team2++; + + gi.sound (ent, CHAN_RELIABLE+CHAN_NO_PHS_ADD+CHAN_VOICE, gi.soundindex("ctf/flagcap.wav"), 1, ATTN_NONE, 0); + + // other gets another 10 frag bonus + other->client->resp.score += CTF_CAPTURE_BONUS; + + // Ok, let's do the player loop, hand out the bonuses + for (i = 1; i <= maxclients->value; i++) { + player = &g_edicts[i]; + if (!player->inuse) + continue; + + if (player->client->resp.ctf_team != other->client->resp.ctf_team) + player->client->resp.ctf_lasthurtcarrier = -5; + else if (player->client->resp.ctf_team == other->client->resp.ctf_team) { + if (player != other) + player->client->resp.score += CTF_TEAM_BONUS; + // award extra points for capture assists + if (player->client->resp.ctf_lastreturnedflag + CTF_RETURN_FLAG_ASSIST_TIMEOUT > level.time) { + gi.bprintf(PRINT_HIGH, "%s gets an assist for returning the flag!\n", player->client->pers.netname); + player->client->resp.score += CTF_RETURN_FLAG_ASSIST_BONUS; + } + if (player->client->resp.ctf_lastfraggedcarrier + CTF_FRAG_CARRIER_ASSIST_TIMEOUT > level.time) { + gi.bprintf(PRINT_HIGH, "%s gets an assist for fragging the flag carrier!\n", player->client->pers.netname); + player->client->resp.score += CTF_FRAG_CARRIER_ASSIST_BONUS; + } + } + } + + CTFResetFlags(); + return false; + } + return false; // its at home base already + } + // hey, its not home. return it by teleporting it back + gi.bprintf(PRINT_HIGH, "%s returned the %s flag!\n", + other->client->pers.netname, CTFTeamName(ctf_team)); + other->client->resp.score += CTF_RECOVERY_BONUS; + other->client->resp.ctf_lastreturnedflag = level.time; + gi.sound (ent, CHAN_RELIABLE+CHAN_NO_PHS_ADD+CHAN_VOICE, gi.soundindex("ctf/flagret.wav"), 1, ATTN_NONE, 0); + //CTFResetFlag will remove this entity! We must return false + CTFResetFlag(ctf_team); + return false; + } + + // hey, its not our flag, pick it up + gi.bprintf(PRINT_HIGH, "%s got the %s flag!\n", + other->client->pers.netname, CTFTeamName(ctf_team)); + other->client->resp.score += CTF_FLAG_BONUS; + + other->client->pers.inventory[ITEM_INDEX(flag_item)] = 1; + other->client->resp.ctf_flagsince = level.time; + + // pick up the flag + // if it's not a dropped flag, we just make is disappear + // if it's dropped, it will be removed by the pickup caller + if (!(ent->spawnflags & DROPPED_ITEM)) { + ent->flags |= FL_RESPAWN; + ent->svflags |= SVF_NOCLIENT; + ent->solid = SOLID_NOT; + } + return true; +} + +static void CTFDropFlagTouch(edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + //owner (who dropped us) can't touch for two secs + if (other == ent->owner && + ent->nextthink - level.time > CTF_AUTO_FLAG_RETURN_TIMEOUT-2) + return; + + Touch_Item (ent, other, plane, surf); +} + +static void CTFDropFlagThink(edict_t *ent) +{ + // auto return the flag + // reset flag will remove ourselves + if (strcmp(ent->classname, "item_flag_team1") == 0) { + CTFResetFlag(CTF_TEAM1); + gi.bprintf(PRINT_HIGH, "The %s flag has returned!\n", + CTFTeamName(CTF_TEAM1)); + } else if (strcmp(ent->classname, "item_flag_team2") == 0) { + CTFResetFlag(CTF_TEAM2); + gi.bprintf(PRINT_HIGH, "The %s flag has returned!\n", + CTFTeamName(CTF_TEAM2)); + } +} + +// Called from PlayerDie, to drop the flag from a dying player +void CTFDeadDropFlag(edict_t *self) +{ + edict_t *dropped = NULL; + + if (!flag1_item || !flag2_item) + CTFInit(); + + if (self->client->pers.inventory[ITEM_INDEX(flag1_item)]) { + dropped = Drop_Item(self, flag1_item); + self->client->pers.inventory[ITEM_INDEX(flag1_item)] = 0; + gi.bprintf(PRINT_HIGH, "%s lost the %s flag!\n", + self->client->pers.netname, CTFTeamName(CTF_TEAM1)); + } else if (self->client->pers.inventory[ITEM_INDEX(flag2_item)]) { + dropped = Drop_Item(self, flag2_item); + self->client->pers.inventory[ITEM_INDEX(flag2_item)] = 0; + gi.bprintf(PRINT_HIGH, "%s lost the %s flag!\n", + self->client->pers.netname, CTFTeamName(CTF_TEAM2)); + } + + if (dropped) { + dropped->think = CTFDropFlagThink; + dropped->nextthink = level.time + CTF_AUTO_FLAG_RETURN_TIMEOUT; + dropped->touch = CTFDropFlagTouch; + } +} + +qboolean CTFDrop_Flag(edict_t *ent, gitem_t *item) +{ + if (rand() & 1) + { + if(!(ent->svflags & SVF_MONSTER)) + gi.cprintf(ent, PRINT_HIGH, "Only lusers drop flags.\n"); + } + else + { + if(!(ent->svflags & SVF_MONSTER)) + gi.cprintf(ent, PRINT_HIGH, "Winners don't drop flags.\n"); + } + return false; +} + +static void CTFFlagThink(edict_t *ent) +{ + if (ent->solid != SOLID_NOT) + ent->s.frame = 173 + (((ent->s.frame - 173) + 1) % 16); + ent->nextthink = level.time + FRAMETIME; +} + +void droptofloor (edict_t *ent); +void SpawnItem3 (edict_t *ent, gitem_t *item); +//edict_t *GetBotFlag1(); //チーム1の旗 +//edict_t *GetBotFlag2(); //チーム2の旗 +void ChainPodThink (edict_t *ent); +qboolean ChkTFlg( void );//旗セットアップ済み? +void SetBotFlag1(edict_t *ent); //チーム1の旗 +void SetBotFlag2(edict_t *ent); //チーム2の旗 + +void CTFFlagSetup (edict_t *ent) +{ + trace_t tr; + vec3_t dest; + float *v; + + v = tv(-15,-15,-15); + VectorCopy (v, ent->mins); + v = tv(15,15,15); + VectorCopy (v, ent->maxs); + + if (ent->model) + gi.setmodel (ent, ent->model); + else if(ent->item->world_model) //PONKO + gi.setmodel (ent, ent->item->world_model); + else ent->s.modelindex = 0; //PONKO + ent->solid = SOLID_TRIGGER; + ent->movetype = MOVETYPE_TOSS; + ent->touch = Touch_Item; + + v = tv(0,0,-128); + VectorAdd (ent->s.origin, v, dest); + + tr = gi.trace (ent->s.origin, ent->mins, ent->maxs, dest, ent, MASK_SOLID); + if (tr.startsolid) + { + gi.dprintf ("CTFFlagSetup: %s startsolid at %s\n", ent->classname, vtos(ent->s.origin)); + G_FreeEdict (ent); + return; + } + + VectorCopy (tr.endpos, ent->s.origin); + + gi.linkentity (ent); + + ent->nextthink = level.time + FRAMETIME; + ent->think = CTFFlagThink; + +////PON +// if (Q_stricmp(ent->classname, "item_flag_team1") == 0) SetBotFlag1(ent); +// else if (Q_stricmp(ent->classname, "item_flag_team2") == 0) SetBotFlag2(ent); +////PON + return; +} + +void CTFEffects(edict_t *player) +{ + player->s.effects &= ~(EF_FLAG1 | EF_FLAG2); + if (player->health > 0) { + if (player->client->pers.inventory[ITEM_INDEX(flag1_item)]) { + player->s.effects |= EF_FLAG1; + } + if (player->client->pers.inventory[ITEM_INDEX(flag2_item)] + || player->client->pers.inventory[ITEM_INDEX(zflag_item)]) { + player->s.effects |= EF_FLAG2; + } + } + + if (player->client->pers.inventory[ITEM_INDEX(flag1_item)]) + player->s.modelindex3 = gi.modelindex("players/male/flag1.md2"); + else if (player->client->pers.inventory[ITEM_INDEX(flag2_item)]) + player->s.modelindex3 = gi.modelindex("players/male/flag2.md2"); + else if (player->client->pers.inventory[ITEM_INDEX(zflag_item)]) + { + player->s.modelindex3 = gi.modelindex("models/zflag.md2"); + } + else + player->s.modelindex3 = 0; +} + +// called when we enter the intermission +void CTFCalcScores(void) +{ + int i; + + ctfgame.total1 = ctfgame.total2 = 0; + for (i = 0; i < maxclients->value; i++) { + if (!g_edicts[i+1].inuse) + continue; + if (game.clients[i].resp.ctf_team == CTF_TEAM1) + ctfgame.total1 += game.clients[i].resp.score; + else if (game.clients[i].resp.ctf_team == CTF_TEAM2) + ctfgame.total2 += game.clients[i].resp.score; + } +} + +void CTFID_f (edict_t *ent) +{ + if (ent->client->resp.id_state) { + if(!(ent->svflags & SVF_MONSTER)) + gi.cprintf(ent, PRINT_HIGH, "Disabling player identication display.\n"); + ent->client->resp.id_state = false; + } else { + if(!(ent->svflags & SVF_MONSTER)) + gi.cprintf(ent, PRINT_HIGH, "Activating player identication display.\n"); + ent->client->resp.id_state = true; + } +} + +static void CTFSetIDView(edict_t *ent) +{ + vec3_t forward, dir; + trace_t tr; + edict_t *who, *best; + float bd = 0, d; + int i; + + ent->client->ps.stats[STAT_CTF_ID_VIEW] = 0; + + AngleVectors(ent->client->v_angle, forward, NULL, NULL); + VectorScale(forward, 1024, forward); + VectorAdd(ent->s.origin, forward, forward); + tr = gi.trace(ent->s.origin, NULL, NULL, forward, ent, MASK_SOLID); + if (tr.fraction < 1 && tr.ent && tr.ent->client) { + ent->client->ps.stats[STAT_CTF_ID_VIEW] = + CS_PLAYERSKINS + (ent - g_edicts - 1); + return; + } + + AngleVectors(ent->client->v_angle, forward, NULL, NULL); + best = NULL; + for (i = 1; i <= maxclients->value; i++) { + who = g_edicts + i; + if (!who->inuse) + continue; + VectorSubtract(who->s.origin, ent->s.origin, dir); + VectorNormalize(dir); + d = DotProduct(forward, dir); + if (d > bd && loc_CanSee(ent, who)) { + bd = d; + best = who; + } + } + if (bd > 0.90) + ent->client->ps.stats[STAT_CTF_ID_VIEW] = + CS_PLAYERSKINS + (best - g_edicts - 1); +} + +void SetCTFStats(edict_t *ent) +{ + gitem_t *tech; + int i; + int p1, p2; + edict_t *e; + + // logo headers for the frag display + ent->client->ps.stats[STAT_CTF_TEAM1_HEADER] = gi.imageindex ("ctfsb1"); + ent->client->ps.stats[STAT_CTF_TEAM2_HEADER] = gi.imageindex ("ctfsb2"); + + // if during intermission, we must blink the team header of the winning team + if (level.intermissiontime && (level.framenum & 8)) { // blink 1/8th second + // note that ctfgame.total[12] is set when we go to intermission + if (ctfgame.team1 > ctfgame.team2) + ent->client->ps.stats[STAT_CTF_TEAM1_HEADER] = 0; + else if (ctfgame.team2 > ctfgame.team1) + ent->client->ps.stats[STAT_CTF_TEAM2_HEADER] = 0; + else if (ctfgame.total1 > ctfgame.total2) // frag tie breaker + ent->client->ps.stats[STAT_CTF_TEAM1_HEADER] = 0; + else if (ctfgame.total2 > ctfgame.total1) + ent->client->ps.stats[STAT_CTF_TEAM2_HEADER] = 0; + else { // tie game! + ent->client->ps.stats[STAT_CTF_TEAM1_HEADER] = 0; + ent->client->ps.stats[STAT_CTF_TEAM2_HEADER] = 0; + } + } + + // tech icon + i = 0; + ent->client->ps.stats[STAT_CTF_TECH] = 0; + while (tnames[i]) { + if ((tech = FindItemByClassname(tnames[i])) != NULL && + ent->client->pers.inventory[ITEM_INDEX(tech)]) { + ent->client->ps.stats[STAT_CTF_TECH] = gi.imageindex(tech->icon); + break; + } + i++; + } + + // figure out what icon to display for team logos + // three states: + // flag at base + // flag taken + // flag dropped + p1 = gi.imageindex ("i_ctf1"); + e = G_Find(NULL, FOFS(classname), "item_flag_team1"); + if (e != NULL) { + if (e->solid == SOLID_NOT) { + int i; + + // not at base + // check if on player + p1 = gi.imageindex ("i_ctf1d"); // default to dropped + for (i = 1; i <= maxclients->value; i++) + if (g_edicts[i].inuse && + g_edicts[i].client->pers.inventory[ITEM_INDEX(flag1_item)]) { + // enemy has it + p1 = gi.imageindex ("i_ctf1t"); + break; + } + } else if (e->spawnflags & DROPPED_ITEM) + p1 = gi.imageindex ("i_ctf1d"); // must be dropped + } + p2 = gi.imageindex ("i_ctf2"); + e = G_Find(NULL, FOFS(classname), "item_flag_team2"); + if (e != NULL) { + if (e->solid == SOLID_NOT) { + int i; + + // not at base + // check if on player + p2 = gi.imageindex ("i_ctf2d"); // default to dropped + for (i = 1; i <= maxclients->value; i++) + if (g_edicts[i].inuse && + g_edicts[i].client->pers.inventory[ITEM_INDEX(flag2_item)]) { + // enemy has it + p2 = gi.imageindex ("i_ctf2t"); + break; + } + } else if (e->spawnflags & DROPPED_ITEM) + p2 = gi.imageindex ("i_ctf2d"); // must be dropped + } + + + ent->client->ps.stats[STAT_CTF_TEAM1_PIC] = p1; + ent->client->ps.stats[STAT_CTF_TEAM2_PIC] = p2; + + if (ctfgame.last_flag_capture && level.time - ctfgame.last_flag_capture < 5) { + if (ctfgame.last_capture_team == CTF_TEAM1) + if (level.framenum & 8) + ent->client->ps.stats[STAT_CTF_TEAM1_PIC] = p1; + else + ent->client->ps.stats[STAT_CTF_TEAM1_PIC] = 0; + else + if (level.framenum & 8) + ent->client->ps.stats[STAT_CTF_TEAM2_PIC] = p2; + else + ent->client->ps.stats[STAT_CTF_TEAM2_PIC] = 0; + } + + ent->client->ps.stats[STAT_CTF_TEAM1_CAPS] = ctfgame.team1; + ent->client->ps.stats[STAT_CTF_TEAM2_CAPS] = ctfgame.team2; + + ent->client->ps.stats[STAT_CTF_FLAG_PIC] = 0; + if (ent->client->resp.ctf_team == CTF_TEAM1 && + ent->client->pers.inventory[ITEM_INDEX(flag2_item)] && + (level.framenum & 8)) + ent->client->ps.stats[STAT_CTF_FLAG_PIC] = gi.imageindex ("i_ctf2"); + + else if (ent->client->resp.ctf_team == CTF_TEAM2 && + ent->client->pers.inventory[ITEM_INDEX(flag1_item)] && + (level.framenum & 8)) + ent->client->ps.stats[STAT_CTF_FLAG_PIC] = gi.imageindex ("i_ctf1"); + + ent->client->ps.stats[STAT_CTF_JOINED_TEAM1_PIC] = 0; + ent->client->ps.stats[STAT_CTF_JOINED_TEAM2_PIC] = 0; + if (ent->client->resp.ctf_team == CTF_TEAM1) + ent->client->ps.stats[STAT_CTF_JOINED_TEAM1_PIC] = gi.imageindex ("i_ctfj"); + else if (ent->client->resp.ctf_team == CTF_TEAM2) + ent->client->ps.stats[STAT_CTF_JOINED_TEAM2_PIC] = gi.imageindex ("i_ctfj"); + + ent->client->ps.stats[STAT_CTF_ID_VIEW] = 0; + if (ent->client->resp.id_state) + CTFSetIDView(ent); +} + +/*------------------------------------------------------------------------*/ + +/*QUAKED info_player_team1 (1 0 0) (-16 -16 -24) (16 16 32) +potential team1 spawning position for ctf games +*/ +void SP_info_player_team1(edict_t *self) +{ +} + +/*QUAKED info_player_team2 (0 0 1) (-16 -16 -24) (16 16 32) +potential team2 spawning position for ctf games +*/ +void SP_info_player_team2(edict_t *self) +{ +} + + +/*------------------------------------------------------------------------*/ +/* GRAPPLE */ +/*------------------------------------------------------------------------*/ + +// ent is player +void CTFPlayerResetGrapple(edict_t *ent) +{ + int i; + edict_t *e; + vec3_t v,vv; + +//PON-CTF + if(chedit->value && ent == &g_edicts[1] && ent->client->ctf_grapple) + { + e = (edict_t*)ent->client->ctf_grapple; + VectorCopy(e->s.origin,vv); + + for(i = 1;(CurrentIndex - i) > 0;i++) + { + if(Route[CurrentIndex - i].state == GRS_GRAPHOOK) break; + else if(Route[CurrentIndex - i].state == GRS_GRAPSHOT) break; + } + if(Route[CurrentIndex - i].state == GRS_GRAPHOOK) + { + Route[CurrentIndex].state = GRS_GRAPRELEASE; + VectorCopy(ent->s.origin,Route[CurrentIndex].Pt); + VectorSubtract(ent->s.origin,vv,v); + Route[CurrentIndex].Tcourner[0] = VectorLength(v); +//gi.bprintf(PRINT_HIGH,"length %f\n",VectorLength(v)); + } + else if(Route[CurrentIndex - i].state == GRS_GRAPSHOT) + { + CurrentIndex = CurrentIndex - i -1; + } +//gi.bprintf(PRINT_HIGH,"length %f\n",VectorLength(v)); + + if((CurrentIndex - i) > 0) + { + if(++CurrentIndex < MAXNODES) + { + gi.bprintf(PRINT_HIGH,"Grapple has been released.Last %i pod(s).\n",MAXNODES - CurrentIndex); + memset(&Route[CurrentIndex],0,sizeof(route_t)); //initialize + Route[CurrentIndex].index = Route[CurrentIndex - 1].index +1; + } + } + } +//PON-CTF + + if (ent->client && ent->client->ctf_grapple) + CTFResetGrapple(ent->client->ctf_grapple); + ent->s.sound = 0; +} + +// self is grapple, not player +void CTFResetGrapple(edict_t *self) +{ + self->s.sound = 0; + + if(self->owner == NULL) + { + G_FreeEdict(self); + return; + } + + if (self->owner->client->ctf_grapple) { + float volume = 1.0; + gclient_t *cl; + + if (self->owner->client->silencer_shots) + volume = 0.2; + + gi.sound (self->owner, CHAN_RELIABLE+CHAN_WEAPON, gi.soundindex("weapons/grapple/grreset.wav"), volume, ATTN_NORM, 0); + cl = self->owner->client; + cl->ctf_grapple = NULL; + cl->ctf_grapplereleasetime = level.time; + cl->ctf_grapplestate = CTF_GRAPPLE_STATE_FLY; // we're firing, not on hook + cl->ps.pmove.pm_flags &= ~PMF_NO_PREDICTION; + G_FreeEdict(self); + } +} + +void CTFGrappleTouch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + short i; + float volume = 1.0; + + if (other == self->owner) + return; + + if (self->owner->client->ctf_grapplestate != CTF_GRAPPLE_STATE_FLY) + return; + + if (surf && (surf->flags & SURF_SKY)) + { + CTFResetGrapple(self); + return; + } + + VectorCopy(vec3_origin, self->velocity); + + PlayerNoise(self->owner, self->s.origin, PNOISE_IMPACT); + + if (other->takedamage) { + T_Damage (other, self, self->owner, self->velocity, self->s.origin, plane->normal, self->dmg, 1, 0, MOD_GRAPPLE); + CTFResetGrapple(self); + return; + } + + self->owner->client->ctf_grapplestate = CTF_GRAPPLE_STATE_PULL; // we're on hook + self->enemy = other; + +//PON-CTF + if(chedit->value && self->owner == &g_edicts[1]) + { + i = CurrentIndex; + while(--i > 0) + { + if(Route[i].state == GRS_GRAPSHOT) + { + VectorCopy(self->s.origin,Route[i].Tcourner); + break; + } + } + Route[CurrentIndex].state = GRS_GRAPHOOK; + VectorCopy(self->owner->s.origin,Route[CurrentIndex].Pt); + + if(++CurrentIndex < MAXNODES) + { + gi.bprintf(PRINT_HIGH,"Grapple has been hook.Last %i pod(s).\n",MAXNODES - CurrentIndex); + memset(&Route[CurrentIndex],0,sizeof(route_t)); //initialize + Route[CurrentIndex].index = Route[CurrentIndex - 1].index +1; + } + } +//PON-CTF + + self->solid = SOLID_NOT; + + if (self->owner->client->silencer_shots) + volume = 0.2; + + gi.sound (self->owner, CHAN_RELIABLE+CHAN_WEAPON, gi.soundindex("weapons/grapple/grpull.wav"), volume, ATTN_NORM, 0); + gi.sound (self, CHAN_WEAPON, gi.soundindex("weapons/grapple/grhit.wav"), volume, ATTN_NORM, 0); + + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_SPARKS); + gi.WritePosition (self->s.origin); + if (!plane) + gi.WriteDir (vec3_origin); + else + gi.WriteDir (plane->normal); + gi.multicast (self->s.origin, MULTICAST_PVS); +} + +// draw beam between grapple and self +void CTFGrappleDrawCable(edict_t *self) +{ + vec3_t offset, start, end, f, r; + vec3_t dir; + float distance; + float x; + + if(1/*!(self->owner->svflags & SVF_MONSTER)*/) + { + AngleVectors (self->owner->client->v_angle, f, r, NULL); + VectorSet(offset, 16, 16, self->owner->viewheight-8); + P_ProjectSource (self->owner->client, self->owner->s.origin, offset, f, r, start); + } + else + { + x = self->owner->s.angles[YAW] ; + x = x * M_PI * 2 / 360; + start[0] = self->owner->s.origin[0] + cos(x) * 16; + start[1] = self->owner->s.origin[1] + sin(x) * 16; + if(self->owner->maxs[2] >=32) start[2] = self->owner->s.origin[2]+16; + else start[2] = self->owner->s.origin[2]-12; + } + + VectorSubtract(start, self->owner->s.origin, offset); + + VectorSubtract (start, self->s.origin, dir); + distance = VectorLength(dir); + // don't draw cable if close + if (distance < 64) + return; + +#if 0 + if (distance > 256) + return; + + // check for min/max pitch + vectoangles (dir, angles); + if (angles[0] < -180) + angles[0] += 360; + if (fabs(angles[0]) > 45) + return; + + trace_t tr; //!! + + tr = gi.trace (start, NULL, NULL, self->s.origin, self, MASK_SHOT); + if (tr.ent != self) { + CTFResetGrapple(self); + return; + } +#endif + + // adjust start for beam origin being in middle of a segment +// VectorMA (start, 8, f, start); + + VectorCopy (self->s.origin, end); + // adjust end z for end spot since the monster is currently dead +// end[2] = self->absmin[2] + self->size[2] / 2; + + gi.WriteByte (svc_temp_entity); +#if 1 //def USE_GRAPPLE_CABLE + gi.WriteByte (TE_GRAPPLE_CABLE); + gi.WriteShort (self->owner - g_edicts); + gi.WritePosition (self->owner->s.origin); + gi.WritePosition (end); + gi.WritePosition (offset); +#else + gi.WriteByte (TE_MEDIC_CABLE_ATTACK); + gi.WriteShort (self - g_edicts); + gi.WritePosition (end); + gi.WritePosition (start); +#endif + gi.multicast (self->s.origin, MULTICAST_PVS); +} + +void SV_AddGravity (edict_t *ent); + +// pull the player toward the grapple +void CTFGrapplePull(edict_t *self) +{ + vec3_t hookdir, v; + float vlen; + + if(self->owner == NULL) + { + CTFResetGrapple(self); + return; + } + +/* if(!(self->owner->svflags & SVF_MONSTER)) + { + if (strcmp(self->owner->client->pers.weapon->classname, "weapon_grapple") == 0 && + !self->owner->client->newweapon && + self->owner->client->weaponstate != WEAPON_FIRING && + self->owner->client->weaponstate != WEAPON_ACTIVATING) + { + CTFResetGrapple(self); + return; + } + }*/ + + if (self->enemy) { + if (self->enemy->solid == SOLID_NOT) { + CTFResetGrapple(self); + return; + } + if (self->enemy->solid == SOLID_BBOX) { + VectorScale(self->enemy->size, 0.5, v); + VectorAdd(v, self->enemy->s.origin, v); + VectorAdd(v, self->enemy->mins, self->s.origin); + gi.linkentity (self); + } else + VectorCopy(self->enemy->velocity, self->velocity); + if (self->enemy->takedamage && + !CheckTeamDamage (self->enemy, self->owner)) { + float volume = 1.0; + + if (self->owner->client->silencer_shots) + volume = 0.2; + + T_Damage (self->enemy, self, self->owner, self->velocity, self->s.origin, vec3_origin, 1, 1, 0, MOD_GRAPPLE); + gi.sound (self, CHAN_WEAPON, gi.soundindex("weapons/grapple/grhurt.wav"), volume, ATTN_NORM, 0); + } + if (self->enemy->deadflag) { // he died + CTFResetGrapple(self); + return; + } + } + + CTFGrappleDrawCable(self); + + if (self->owner->client->ctf_grapplestate > CTF_GRAPPLE_STATE_FLY) { + // pull player toward grapple + // this causes icky stuff with prediction, we need to extend + // the prediction layer to include two new fields in the player + // move stuff: a point and a velocity. The client should add + // that velociy in the direction of the point + vec3_t forward, up; + + if(1/*!(self->owner->svflags & SVF_MONSTER)*/) + { + AngleVectors (self->owner->client->v_angle, forward, NULL, up); + VectorCopy(self->owner->s.origin, v); + v[2] += self->owner->viewheight; + VectorSubtract (self->s.origin, v, hookdir); + } + else + { + VectorCopy(self->owner->s.origin, v); + if(self->owner->maxs[2] >=32) v[2] += 16; + else v[2] -= 12; + VectorSubtract (self->s.origin, v, hookdir); + } + + vlen = VectorLength(hookdir); + + if (self->owner->client->ctf_grapplestate == CTF_GRAPPLE_STATE_PULL && + vlen < 64) { + float volume = 1.0; + + if (self->owner->client->silencer_shots) + volume = 0.2; + + self->owner->client->ps.pmove.pm_flags |= PMF_NO_PREDICTION; + gi.sound (self->owner, CHAN_RELIABLE+CHAN_WEAPON, gi.soundindex("weapons/grapple/grhang.wav"), volume, ATTN_NORM, 0); + self->owner->client->ctf_grapplestate = CTF_GRAPPLE_STATE_HANG; + } + + VectorNormalize (hookdir); + VectorScale(hookdir, CTF_GRAPPLE_PULL_SPEED, hookdir); + VectorCopy(hookdir, self->owner->velocity); + SV_AddGravity(self->owner); + } +} + +void CTFFireGrapple (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int effect) +{ + edict_t *grapple; + trace_t tr; + + VectorNormalize (dir); + + grapple = G_Spawn(); + VectorCopy (start, grapple->s.origin); + VectorCopy (start, grapple->s.old_origin); + vectoangles (dir, grapple->s.angles); + VectorScale (dir, speed, grapple->velocity); + grapple->movetype = MOVETYPE_FLYMISSILE; + grapple->clipmask = MASK_SHOT; + grapple->solid = SOLID_BBOX; + grapple->s.effects |= effect; + VectorClear (grapple->mins); + VectorClear (grapple->maxs); + grapple->s.modelindex = gi.modelindex ("models/weapons/grapple/hook/tris.md2"); +// grapple->s.sound = gi.soundindex ("misc/lasfly.wav"); + grapple->owner = self; + grapple->touch = CTFGrappleTouch; +// grapple->nextthink = level.time + FRAMETIME; +// grapple->think = CTFGrappleThink; + grapple->dmg = damage; + self->client->ctf_grapple = grapple; + self->client->ctf_grapplestate = CTF_GRAPPLE_STATE_FLY; // we're firing, not on hook + gi.linkentity (grapple); + + tr = gi.trace (self->s.origin, NULL, NULL, grapple->s.origin, grapple, MASK_SHOT); + if (tr.fraction < 1.0) + { + VectorMA (grapple->s.origin, -10, dir, grapple->s.origin); + grapple->touch (grapple, tr.ent, NULL, NULL); + } +//PON-CTF + if(chedit->value && self == &g_edicts[1]) + { + Route[CurrentIndex].state = GRS_GRAPSHOT; + VectorCopy(self->s.origin,Route[CurrentIndex].Pt); + + if(++CurrentIndex < MAXNODES) + { + gi.bprintf(PRINT_HIGH,"Grapple has been shoot.Last %i pod(s).\n",MAXNODES - CurrentIndex); + memset(&Route[CurrentIndex],0,sizeof(route_t)); //initialize + Route[CurrentIndex].index = Route[CurrentIndex - 1].index +1; + } + } +//PON-CTF + +} + +void CTFGrappleFire (edict_t *ent, vec3_t g_offset, int damage, int effect) +{ + vec3_t forward, right; + vec3_t start; + vec3_t offset; + float volume = 1.0; + + if (ent->client->ctf_grapplestate > CTF_GRAPPLE_STATE_FLY) + return; // it's already out + + AngleVectors (ent->client->v_angle, forward, right, NULL); +// VectorSet(offset, 24, 16, ent->viewheight-8+2); + VectorSet(offset, 24, 8, ent->viewheight-8+2); + VectorAdd (offset, g_offset, offset); + P_ProjectSource (ent->client, ent->s.origin, offset, forward, right, start); + + VectorScale (forward, -2, ent->client->kick_origin); + ent->client->kick_angles[0] = -1; + + if (ent->client->silencer_shots) + volume = 0.2; + + gi.sound (ent, CHAN_RELIABLE+CHAN_WEAPON, gi.soundindex("weapons/grapple/grfire.wav"), volume, ATTN_NORM, 0); + CTFFireGrapple (ent, start, forward, damage, CTF_GRAPPLE_SPEED, effect); + +#if 0 + // send muzzle flash + gi.WriteByte (svc_muzzleflash); + gi.WriteShort (ent-g_edicts); + gi.WriteByte (MZ_BLASTER); + gi.multicast (ent->s.origin, MULTICAST_PVS); +#endif + + PlayerNoise(ent, start, PNOISE_WEAPON); +} + + +void CTFWeapon_Grapple_Fire (edict_t *ent) +{ + int damage; + + damage = 10; + CTFGrappleFire (ent, vec3_origin, damage, 0); + ent->client->ps.gunframe++; +} + +void CTFWeapon_Grapple (edict_t *ent) +{ + static int pause_frames[] = {10, 18, 27, 0}; + static int fire_frames[] = {6, 0}; + int prevstate,i; + vec3_t v,vv; + edict_t *e; + + // if the the attack button is still down, stay in the firing frame + if ((ent->client->buttons & BUTTON_ATTACK) && + ent->client->weaponstate == WEAPON_FIRING && + ent->client->ctf_grapple) + ent->client->ps.gunframe = 9; + + if (!(ent->client->buttons & BUTTON_ATTACK) && + ent->client->ctf_grapple) + { + i = ent->client->ctf_grapplestate; + e = (edict_t*)ent->client->ctf_grapple; + VectorCopy(e->s.origin,vv); + CTFResetGrapple(ent->client->ctf_grapple); + if (ent->client->weaponstate == WEAPON_FIRING) + ent->client->weaponstate = WEAPON_READY; + + if(ent->svflags & SVF_MONSTER) + { + if(ent->waterlevel + || i == CTF_GRAPPLE_STATE_HANG ) VectorClear(ent->velocity); + //fix the moving speed + if(i == CTF_GRAPPLE_STATE_PULL) + { + v[0] = ent->velocity[0]; + v[1] = ent->velocity[1]; + v[2] = 0; + + ent->client->zc.moveyaw = Get_yaw(v); + ent->moveinfo.speed = (VectorLength(v) * FRAMETIME) / MOVE_SPD_RUN; + ent->velocity[0] = 0; + ent->velocity[1] = 0; + } + else if(i == CTF_GRAPPLE_STATE_HANG ) + { + ent->moveinfo.speed = 0; + ent->velocity[0] = 0; + ent->velocity[1] = 0; + } + } +//PON-CTF + if(chedit->value && ent == &g_edicts[1]) + { + for(i = 0;(CurrentIndex - i) > 0;i++) + { + if(Route[CurrentIndex - i].state == GRS_GRAPHOOK) break; + else if(Route[CurrentIndex - i].state == GRS_GRAPSHOT) break; + } + if(Route[CurrentIndex - i].state == GRS_GRAPHOOK) + { + Route[CurrentIndex].state = GRS_GRAPRELEASE; + VectorCopy(ent->s.origin,Route[CurrentIndex].Pt); + VectorSubtract(ent->s.origin,vv,v); + Route[CurrentIndex].Tcourner[0] = VectorLength(v); +//gi.bprintf(PRINT_HIGH,"length %f\n",VectorLength(v)); + } + else if(Route[CurrentIndex - i].state == GRS_GRAPSHOT) + { + CurrentIndex = CurrentIndex - i -1; + } +//gi.bprintf(PRINT_HIGH,"length %f\n",VectorLength(v)); + + if((CurrentIndex - i) > 0) + { + if(++CurrentIndex < MAXNODES) + { + gi.bprintf(PRINT_HIGH,"Grapple has been released.Last %i pod(s).\n",MAXNODES - CurrentIndex); + memset(&Route[CurrentIndex],0,sizeof(route_t)); //initialize + Route[CurrentIndex].index = Route[CurrentIndex - 1].index +1; + } + } + } +//PON-CTF + + } + + + if (ent->client->newweapon && + ent->client->ctf_grapplestate > CTF_GRAPPLE_STATE_FLY && + ent->client->weaponstate == WEAPON_FIRING) { + // he wants to change weapons while grappled + ent->client->weaponstate = WEAPON_DROPPING; + ent->client->ps.gunframe = 32; + } + + prevstate = ent->client->weaponstate; + Weapon_Generic (ent, 5, 9, 31, 36, pause_frames, fire_frames, + CTFWeapon_Grapple_Fire); + + + if(ent->svflags & SVF_MONSTER) + { + return; + } + + // if we just switched back to grapple, immediately go to fire frame + if (prevstate == WEAPON_ACTIVATING && + ent->client->weaponstate == WEAPON_READY && + ent->client->ctf_grapplestate > CTF_GRAPPLE_STATE_FLY) { + if (!(ent->client->buttons & BUTTON_ATTACK)) + ent->client->ps.gunframe = 9; + else + ent->client->ps.gunframe = 5; + ent->client->weaponstate = WEAPON_FIRING; + } +} + +void CTFTeam_f (edict_t *ent) +{ + char *t, *s; + int desired_team; + + t = gi.args(); + if (!*t) { + if(!(ent->svflags & SVF_MONSTER)) + gi.cprintf(ent, PRINT_HIGH, "You are on the %s team.\n", + CTFTeamName(ent->client->resp.ctf_team)); + return; + } + if (Q_stricmp(t, "red") == 0) + desired_team = CTF_TEAM1; + else if (Q_stricmp(t, "blue") == 0) + desired_team = CTF_TEAM2; + else { + if(!(ent->svflags & SVF_MONSTER)) + gi.cprintf(ent, PRINT_HIGH, "Unknown team %s.\n", t); + return; + } + + if (ent->client->resp.ctf_team == desired_team) { + if(!(ent->svflags & SVF_MONSTER)) + gi.cprintf(ent, PRINT_HIGH, "You are already on the %s team.\n", + CTFTeamName(ent->client->resp.ctf_team)); + return; + } + +//// + ent->svflags = 0; + ent->flags &= ~FL_GODMODE; + ent->client->resp.ctf_team = desired_team; + ent->client->resp.ctf_state = CTF_STATE_START; + s = Info_ValueForKey (ent->client->pers.userinfo, "skin"); + CTFAssignSkin(ent, s); + + if (ent->solid == SOLID_NOT) { // spectator + PutClientInServer (ent); + // add a teleportation effect + ent->s.event = EV_PLAYER_TELEPORT; + // hold in place briefly + ent->client->ps.pmove.pm_flags = PMF_TIME_TELEPORT; + ent->client->ps.pmove.pm_time = 14; + gi.bprintf(PRINT_HIGH, "%s joined the %s team.\n", + ent->client->pers.netname, CTFTeamName(desired_team)); + return; + } + + ent->health = 0; + player_die (ent, ent, ent, 100000, vec3_origin); + // don't even bother waiting for death frames + ent->deadflag = DEAD_DEAD; + respawn (ent); + + ent->client->resp.score = 0; + + gi.bprintf(PRINT_HIGH, "%s changed to the %s team.\n", + ent->client->pers.netname, CTFTeamName(desired_team)); +} + +/* +================== +CTFScoreboardMessage +================== +*/ +void CTFScoreboardMessage (edict_t *ent, edict_t *killer) +{ + char entry[1024]; + char string[1400]; + int len; + int i, j, k, n; + int sorted[2][MAX_CLIENTS]; + int sortedscores[2][MAX_CLIENTS]; + int score, total[2], totalscore[2]; + int last[2]; + gclient_t *cl; + edict_t *cl_ent; + int team; + int maxsize = 1000; + + // sort the clients by team and score + total[0] = total[1] = 0; + last[0] = last[1] = 0; + totalscore[0] = totalscore[1] = 0; + for (i=0 ; iinuse) + continue; + if (game.clients[i].resp.ctf_team == CTF_TEAM1) + team = 0; + else if (game.clients[i].resp.ctf_team == CTF_TEAM2) + team = 1; + else + continue; // unknown team? + + score = game.clients[i].resp.score; + for (j=0 ; j sortedscores[team][j]) + break; + } + for (k=total[team] ; k>j ; k--) + { + sorted[team][k] = sorted[team][k-1]; + sortedscores[team][k] = sortedscores[team][k-1]; + } + sorted[team][j] = i; + sortedscores[team][j] = score; + totalscore[team] += score; + total[team]++; + } + + // print level name and exit rules + // add the clients in sorted order + *string = 0; + len = 0; + + // team one + sprintf(string, "if 24 xv 8 yv 8 pic 24 endif " + "xv 40 yv 28 string \"%4d/%-3d\" " + "xv 98 yv 12 num 2 18 " + "if 25 xv 168 yv 8 pic 25 endif " + "xv 200 yv 28 string \"%4d/%-3d\" " + "xv 256 yv 12 num 2 20 ", + totalscore[0], total[0], + totalscore[1], total[1]); + len = strlen(string); + + for (i=0 ; i<16 ; i++) + { + if (i >= total[0] && i >= total[1]) + break; // we're done + +#if 0 //ndef NEW_SCORE + // set up y + sprintf(entry, "yv %d ", 42 + i * 8); + if (maxsize - len > strlen(entry)) { + strcat(string, entry); + len = strlen(string); + } +#else + *entry = 0; +#endif + + // left side + if (i < total[0]) { + cl = &game.clients[sorted[0][i]]; + cl_ent = g_edicts + 1 + sorted[0][i]; + +#if 0 //ndef NEW_SCORE + sprintf(entry+strlen(entry), + "xv 0 %s \"%3d %3d %-12.12s\" ", + (cl_ent == ent) ? "string2" : "string", + cl->resp.score, + (cl->ping > 999) ? 999 : cl->ping, + cl->pers.netname); + + if (cl_ent->client->pers.inventory[ITEM_INDEX(flag2_item)]) + strcat(entry, "xv 56 picn sbfctf2 "); +#else + sprintf(entry+strlen(entry), + "ctf 0 %d %d %d %d ", + 42 + i * 8, + sorted[0][i], + cl->resp.score, + cl->ping > 999 ? 999 : cl->ping); + + if (cl_ent->client->pers.inventory[ITEM_INDEX(flag2_item)]) + sprintf(entry + strlen(entry), "xv 56 yv %d picn sbfctf2 ", + 42 + i * 8); +#endif + + if (maxsize - len > strlen(entry)) { + strcat(string, entry); + len = strlen(string); + last[0] = i; + } + } + + // right side + if (i < total[1]) { + cl = &game.clients[sorted[1][i]]; + cl_ent = g_edicts + 1 + sorted[1][i]; + +#if 0 //ndef NEW_SCORE + sprintf(entry+strlen(entry), + "xv 160 %s \"%3d %3d %-12.12s\" ", + (cl_ent == ent) ? "string2" : "string", + cl->resp.score, + (cl->ping > 999) ? 999 : cl->ping, + cl->pers.netname); + + if (cl_ent->client->pers.inventory[ITEM_INDEX(flag1_item)]) + strcat(entry, "xv 216 picn sbfctf1 "); + +#else + + sprintf(entry+strlen(entry), + "ctf 160 %d %d %d %d ", + 42 + i * 8, + sorted[1][i], + cl->resp.score, + cl->ping > 999 ? 999 : cl->ping); + + if (cl_ent->client->pers.inventory[ITEM_INDEX(flag1_item)]) + sprintf(entry + strlen(entry), "xv 216 yv %d picn sbfctf1 ", + 42 + i * 8); +#endif + if (maxsize - len > strlen(entry)) { + strcat(string, entry); + len = strlen(string); + last[1] = i; + } + } + } + + // put in spectators if we have enough room + if (last[0] > last[1]) + j = last[0]; + else + j = last[1]; + j = (j + 2) * 8 + 42; + + k = n = 0; + if (maxsize - len > 50) { + for (i = 0; i < maxclients->value; i++) { + cl_ent = g_edicts + 1 + i; + cl = &game.clients[i]; + if (!cl_ent->inuse || + cl_ent->solid != SOLID_NOT || + cl_ent->client->resp.ctf_team != CTF_NOTEAM) + continue; + + if (!k) { + k = 1; + sprintf(entry, "xv 0 yv %d string2 \"Spectators\" ", j); + strcat(string, entry); + len = strlen(string); + j += 8; + } + + sprintf(entry+strlen(entry), + "ctf %d %d %d %d %d ", + (n & 1) ? 160 : 0, // x + j, // y + i, // playernum + cl->resp.score, + cl->ping > 999 ? 999 : cl->ping); + if (maxsize - len > strlen(entry)) { + strcat(string, entry); + len = strlen(string); + } + + if (n & 1) + j += 8; + n++; + } + } + + if (total[0] - last[0] > 1) // couldn't fit everyone + sprintf(string + strlen(string), "xv 8 yv %d string \"..and %d more\" ", + 42 + (last[0]+1)*8, total[0] - last[0] - 1); + if (total[1] - last[1] > 1) // couldn't fit everyone + sprintf(string + strlen(string), "xv 168 yv %d string \"..and %d more\" ", + 42 + (last[1]+1)*8, total[1] - last[1] - 1); + + gi.WriteByte (svc_layout); + gi.WriteString (string); +} + +/*------------------------------------------------------------------------*/ +/* TECH */ +/*------------------------------------------------------------------------*/ + +void CTFHasTech(edict_t *who) +{ + if(who->svflags & SVF_MONSTER) return ; + if (level.time - who->client->ctf_lasttechmsg > 2) { + gi.centerprintf(who, "You already have a TECH powerup."); + who->client->ctf_lasttechmsg = level.time; + } +} + +gitem_t *CTFWhat_Tech(edict_t *ent) +{ + gitem_t *tech; + int i; + + i = 0; + while (tnames[i]) { + if ((tech = FindItemByClassname(tnames[i])) != NULL && + ent->client->pers.inventory[ITEM_INDEX(tech)]) { + return tech; + } + i++; + } + return NULL; +} + +qboolean CTFPickup_Tech (edict_t *ent, edict_t *other) +{ + gitem_t *tech; + int i; + + i = 0; + while (tnames[i]) { + if ((tech = FindItemByClassname(tnames[i])) != NULL && + other->client->pers.inventory[ITEM_INDEX(tech)]) { + CTFHasTech(other); + return false; // has this one + } + i++; + } + + // client only gets one tech + other->client->pers.inventory[ITEM_INDEX(ent->item)]++; + other->client->ctf_regentime = level.time; + return true; +} + +static void SpawnTech(gitem_t *item, edict_t *spot); + +static edict_t *FindTechSpawn(void) +{ + edict_t *spot = NULL; + int i = rand() % 16; + + while (i--) + spot = G_Find (spot, FOFS(classname), "info_player_deathmatch"); + if (!spot) + spot = G_Find (spot, FOFS(classname), "info_player_deathmatch"); + return spot; +} + +static void TechThink(edict_t *tech) +{ + edict_t *spot; + + if ((spot = FindTechSpawn()) != NULL) { + SpawnTech(tech->item, spot); + G_FreeEdict(tech); + } else { + tech->nextthink = level.time + CTF_TECH_TIMEOUT; + tech->think = TechThink; + } +} + +void CTFDrop_Tech(edict_t *ent, gitem_t *item) +{ + edict_t *tech; + + tech = Drop_Item(ent, item); + tech->nextthink = level.time + CTF_TECH_TIMEOUT; + tech->think = TechThink; + ent->client->pers.inventory[ITEM_INDEX(item)] = 0; +} + +void CTFDeadDropTech(edict_t *ent) +{ + gitem_t *tech; + edict_t *dropped; + int i; + + i = 0; + while (tnames[i]) { + if ((tech = FindItemByClassname(tnames[i])) != NULL && + ent->client->pers.inventory[ITEM_INDEX(tech)]) { + dropped = Drop_Item(ent, tech); + // hack the velocity to make it bounce random + dropped->velocity[0] = (rand() % 600) - 300; + dropped->velocity[1] = (rand() % 600) - 300; + dropped->nextthink = level.time + CTF_TECH_TIMEOUT; + dropped->think = TechThink; + dropped->owner = NULL; + ent->client->pers.inventory[ITEM_INDEX(tech)] = 0; + } + i++; + } +} + +static void SpawnTech(gitem_t *item, edict_t *spot) +{ + edict_t *ent; + vec3_t forward, right; + vec3_t angles; + + if(!ctf->value ) return; + + ent = G_Spawn(); + + ent->classname = item->classname; + ent->item = item; + ent->spawnflags = DROPPED_ITEM; + ent->s.effects = item->world_model_flags; + ent->s.renderfx = RF_GLOW; + VectorSet (ent->mins, -15, -15, -15); + VectorSet (ent->maxs, 15, 15, 15); + if(ent->item->world_model) //PONKO + gi.setmodel (ent, ent->item->world_model); + ent->solid = SOLID_TRIGGER; + ent->movetype = MOVETYPE_TOSS; + ent->touch = Touch_Item; + ent->owner = ent; + + angles[0] = 0; + angles[1] = rand() % 360; + angles[2] = 0; + + AngleVectors (angles, forward, right, NULL); + VectorCopy (spot->s.origin, ent->s.origin); + ent->s.origin[2] += 16; + VectorScale (forward, 100, ent->velocity); + ent->velocity[2] = 300; + + ent->nextthink = level.time + CTF_TECH_TIMEOUT; + ent->think = TechThink; + + gi.linkentity (ent); +} + +static void SpawnTechs(edict_t *ent) +{ + gitem_t *tech; + edict_t *spot; + int i; + + i = 0; + while (tnames[i]) { + if ((tech = FindItemByClassname(tnames[i])) != NULL && + (spot = FindTechSpawn()) != NULL) + SpawnTech(tech, spot); + i++; + } +} + +// frees the passed edict! +void CTFRespawnTech(edict_t *ent) +{ + edict_t *spot; + + if ((spot = FindTechSpawn()) != NULL) + SpawnTech(ent->item, spot); + G_FreeEdict(ent); +} + +void CTFSetupTechSpawn(void) +{ + edict_t *ent; + + if (techspawn || ((int)dmflags->value & DF_CTF_NO_TECH)) + return; + + ent = G_Spawn(); + ent->nextthink = level.time + 2; + ent->think = SpawnTechs; + techspawn = true; +} + +int CTFApplyResistance(edict_t *ent, int dmg) +{ + static gitem_t *tech = NULL; + float volume = 1.0; + + if (ent->client && ent->client->silencer_shots) + volume = 0.2; + + if (!tech) + tech = FindItemByClassname("item_tech1"); + if (dmg && tech && ent->client && ent->client->pers.inventory[ITEM_INDEX(tech)]) { + // make noise + gi.sound(ent, CHAN_VOICE, gi.soundindex("ctf/tech1.wav"), volume, ATTN_NORM, 0); + return dmg / 2; + } + return dmg; +} + +int CTFApplyStrength(edict_t *ent, int dmg) +{ + static gitem_t *tech = NULL; + + if (!tech) + tech = FindItemByClassname("item_tech2"); + if (dmg && tech && ent->client && ent->client->pers.inventory[ITEM_INDEX(tech)]) { + return dmg * 2; + } + return dmg; +} + +qboolean CTFApplyStrengthSound(edict_t *ent) +{ + static gitem_t *tech = NULL; + float volume = 1.0; + + if (ent->client && ent->client->silencer_shots) + volume = 0.2; + + if (!tech) + tech = FindItemByClassname("item_tech2"); + if (tech && ent->client && + ent->client->pers.inventory[ITEM_INDEX(tech)]) { + if (ent->client->ctf_techsndtime < level.time) { + ent->client->ctf_techsndtime = level.time + 1; + if (ent->client->quad_framenum > level.framenum) + gi.sound(ent, CHAN_VOICE, gi.soundindex("ctf/tech2x.wav"), volume, ATTN_NORM, 0); + else + gi.sound(ent, CHAN_VOICE, gi.soundindex("ctf/tech2.wav"), volume, ATTN_NORM, 0); + } + return true; + } + return false; +} + + +qboolean CTFApplyHaste(edict_t *ent) +{ + static gitem_t *tech = NULL; + + if (!tech) + tech = FindItemByClassname("item_tech3"); + if (tech && ent->client && + ent->client->pers.inventory[ITEM_INDEX(tech)]) + return true; + return false; +} + +void CTFApplyHasteSound(edict_t *ent) +{ + static gitem_t *tech = NULL; + float volume = 1.0; + + if (ent->client && ent->client->silencer_shots) + volume = 0.2; + + if (!tech) + tech = FindItemByClassname("item_tech3"); + if (tech && ent->client && + ent->client->pers.inventory[ITEM_INDEX(tech)] && + ent->client->ctf_techsndtime < level.time) { + ent->client->ctf_techsndtime = level.time + 1; + gi.sound(ent, CHAN_VOICE, gi.soundindex("ctf/tech3.wav"), volume, ATTN_NORM, 0); + } +} + +void CTFApplyRegeneration(edict_t *ent) +{ + static gitem_t *tech = NULL; + qboolean noise = false; + gclient_t *client; + int index; + float volume = 1.0; + + client = ent->client; + if (!client) + return; + + if (ent->client->silencer_shots) + volume = 0.2; + + if (!tech) + tech = FindItemByClassname("item_tech4"); + if (tech && client->pers.inventory[ITEM_INDEX(tech)]) { + if (client->ctf_regentime < level.time) { + client->ctf_regentime = level.time; + if (ent->health < 150) { + ent->health += 5; + if (ent->health > 150) + ent->health = 150; + client->ctf_regentime += 0.5; + noise = true; + } + index = ArmorIndex (ent); + if (index && client->pers.inventory[index] < 150) { + client->pers.inventory[index] += 5; + if (client->pers.inventory[index] > 150) + client->pers.inventory[index] = 150; + client->ctf_regentime += 0.5; + noise = true; + } + } + if (noise && ent->client->ctf_techsndtime < level.time) { + ent->client->ctf_techsndtime = level.time + 1; + gi.sound(ent, CHAN_VOICE, gi.soundindex("ctf/tech4.wav"), volume, ATTN_NORM, 0); + } + } +} + +qboolean CTFHasRegeneration(edict_t *ent) +{ + static gitem_t *tech = NULL; + + if (!tech) + tech = FindItemByClassname("item_tech4"); + if (tech && ent->client && + ent->client->pers.inventory[ITEM_INDEX(tech)]) + return true; + return false; +} + +/* +====================================================================== + +SAY_TEAM + +====================================================================== +*/ + +// This array is in 'importance order', it indicates what items are +// more important when reporting their names. +struct { + char *classname; + int priority; +} loc_names[] = +{ + { "item_flag_team1", 1 }, + { "item_flag_team2", 1 }, + { "item_quad", 2 }, + { "item_invulnerability", 2 }, + { "weapon_bfg", 3 }, + { "weapon_railgun", 4 }, + { "weapon_rocketlauncher", 4 }, + { "weapon_hyperblaster", 4 }, + { "weapon_chaingun", 4 }, + { "weapon_grenadelauncher", 4 }, + { "weapon_machinegun", 4 }, + { "weapon_supershotgun", 4 }, + { "weapon_shotgun", 4 }, + { "item_power_screen", 5 }, + { "item_power_shield", 5 }, + { "item_armor_body", 6 }, + { "item_armor_combat", 6 }, + { "item_armor_jacket", 6 }, + { "item_silencer", 7 }, + { "item_breather", 7 }, + { "item_enviro", 7 }, + { "item_adrenaline", 7 }, + { "item_bandolier", 8 }, + { "item_pack", 8 }, + { NULL, 0 } +}; + + +static void CTFSay_Team_Location(edict_t *who, char *buf) +{ + edict_t *what = NULL; + edict_t *hot = NULL; + float hotdist = 999999, newdist; + vec3_t v; + int hotindex = 999; + int i; + gitem_t *item; + int nearteam = -1; + edict_t *flag1, *flag2; + qboolean hotsee = false; + qboolean cansee; + + while ((what = loc_findradius(what, who->s.origin, 1024)) != NULL) { + // find what in loc_classnames + for (i = 0; loc_names[i].classname; i++) + if (strcmp(what->classname, loc_names[i].classname) == 0) + break; + if (!loc_names[i].classname) + continue; + // something we can see get priority over something we can't + cansee = loc_CanSee(what, who); + if (cansee && !hotsee) { + hotsee = true; + hotindex = loc_names[i].priority; + hot = what; + VectorSubtract(what->s.origin, who->s.origin, v); + hotdist = VectorLength(v); + continue; + } + // if we can't see this, but we have something we can see, skip it + if (hotsee && !cansee) + continue; + if (hotsee && hotindex < loc_names[i].priority) + continue; + VectorSubtract(what->s.origin, who->s.origin, v); + newdist = VectorLength(v); + if (newdist < hotdist || + (cansee && loc_names[i].priority < hotindex)) { + hot = what; + hotdist = newdist; + hotindex = i; + hotsee = loc_CanSee(hot, who); + } + } + + if (!hot) { + strcpy(buf, "nowhere"); + return; + } + + // we now have the closest item + // see if there's more than one in the map, if so + // we need to determine what team is closest + what = NULL; + while ((what = G_Find(what, FOFS(classname), hot->classname)) != NULL) { + if (what == hot) + continue; + // if we are here, there is more than one, find out if hot + // is closer to red flag or blue flag + if ((flag1 = G_Find(NULL, FOFS(classname), "item_flag_team1")) != NULL && + (flag2 = G_Find(NULL, FOFS(classname), "item_flag_team2")) != NULL) { + VectorSubtract(hot->s.origin, flag1->s.origin, v); + hotdist = VectorLength(v); + VectorSubtract(hot->s.origin, flag2->s.origin, v); + newdist = VectorLength(v); + if (hotdist < newdist) + nearteam = CTF_TEAM1; + else if (hotdist > newdist) + nearteam = CTF_TEAM2; + } + break; + } + + if ((item = FindItemByClassname(hot->classname)) == NULL) { + strcpy(buf, "nowhere"); + return; + } + + // in water? + if (who->waterlevel) + strcpy(buf, "in the water "); + else + *buf = 0; + + // near or above + VectorSubtract(who->s.origin, hot->s.origin, v); + if (fabs(v[2]) > fabs(v[0]) && fabs(v[2]) > fabs(v[1])) + if (v[2] > 0) + strcat(buf, "above "); + else + strcat(buf, "below "); + else + strcat(buf, "near "); + + if (nearteam == CTF_TEAM1) + strcat(buf, "the red "); + else if (nearteam == CTF_TEAM2) + strcat(buf, "the blue "); + else + strcat(buf, "the "); + + strcat(buf, item->pickup_name); +} + +static void CTFSay_Team_Armor(edict_t *who, char *buf) +{ + gitem_t *item; + int index, cells; + int power_armor_type; + + *buf = 0; + + power_armor_type = PowerArmorType (who); + if (power_armor_type) + { + cells = who->client->pers.inventory[ITEM_INDEX(Fdi_CELLS/*FindItem ("cells")*/)]; + if (cells) + sprintf(buf+strlen(buf), "%s with %i cells ", + (power_armor_type == POWER_ARMOR_SCREEN) ? + "Power Screen" : "Power Shield", cells); + } + + index = ArmorIndex (who); + if (index) + { + item = GetItemByIndex (index); + if (item) { + if (*buf) + strcat(buf, "and "); + sprintf(buf+strlen(buf), "%i units of %s", + who->client->pers.inventory[index], item->pickup_name); + } + } + + if (!*buf) + strcpy(buf, "no armor"); +} + +static void CTFSay_Team_Health(edict_t *who, char *buf) +{ + if (who->health <= 0) + strcpy(buf, "dead"); + else + sprintf(buf, "%i health", who->health); +} + +static void CTFSay_Team_Tech(edict_t *who, char *buf) +{ + gitem_t *tech; + int i; + + // see if the player has a tech powerup + i = 0; + while (tnames[i]) { + if ((tech = FindItemByClassname(tnames[i])) != NULL && + who->client->pers.inventory[ITEM_INDEX(tech)]) { + sprintf(buf, "the %s", tech->pickup_name); + return; + } + i++; + } + strcpy(buf, "no powerup"); +} + +static void CTFSay_Team_Weapon(edict_t *who, char *buf) +{ + if (who->client->pers.weapon) + strcpy(buf, who->client->pers.weapon->pickup_name); + else + strcpy(buf, "none"); +} + +static void CTFSay_Team_Sight(edict_t *who, char *buf) +{ + int i; + edict_t *targ; + int n = 0; + char s[1024]; + char s2[1024]; + + *s = *s2 = 0; + for (i = 1; i <= maxclients->value; i++) { + targ = g_edicts + i; + if (!targ->inuse || + targ == who || + !loc_CanSee(targ, who)) + continue; + if (*s2) { + if (strlen(s) + strlen(s2) + 3 < sizeof(s)) { + if (n) + strcat(s, ", "); + strcat(s, s2); + *s2 = 0; + } + n++; + } + strcpy(s2, targ->client->pers.netname); + } + if (*s2) { + if (strlen(s) + strlen(s2) + 6 < sizeof(s)) { + if (n) + strcat(s, " and "); + strcat(s, s2); + } + strcpy(buf, s); + } else + strcpy(buf, "no one"); +} + +void CTFSay_Team(edict_t *who, char *msg) +{ + char outmsg[1024]; + char buf[1024]; + int i; + char *p; + edict_t *cl_ent; + + outmsg[0] = 0; + + if (*msg == '\"') { + msg[strlen(msg) - 1] = 0; + msg++; + } + + for (p = outmsg; *msg && (p - outmsg) < sizeof(outmsg) - 1; msg++) { + if (*msg == '%') { + switch (*++msg) { + case 'l' : + case 'L' : + CTFSay_Team_Location(who, buf); + strcpy(p, buf); + p += strlen(buf); + break; + case 'a' : + case 'A' : + CTFSay_Team_Armor(who, buf); + strcpy(p, buf); + p += strlen(buf); + break; + case 'h' : + case 'H' : + CTFSay_Team_Health(who, buf); + strcpy(p, buf); + p += strlen(buf); + break; + case 't' : + case 'T' : + CTFSay_Team_Tech(who, buf); + strcpy(p, buf); + p += strlen(buf); + break; + case 'w' : + case 'W' : + CTFSay_Team_Weapon(who, buf); + strcpy(p, buf); + p += strlen(buf); + break; + + case 'n' : + case 'N' : + CTFSay_Team_Sight(who, buf); + strcpy(p, buf); + p += strlen(buf); + break; + + default : + *p++ = *msg; + } + } else + *p++ = *msg; + } + *p = 0; + + for (i = 0; i < maxclients->value; i++) { + cl_ent = g_edicts + 1 + i; + if (!cl_ent->inuse) + continue; + if (cl_ent->client->resp.ctf_team == who->client->resp.ctf_team) + { + if(!(cl_ent->svflags & SVF_MONSTER)) + gi.cprintf(cl_ent, PRINT_CHAT, "(%s): %s\n", + who->client->pers.netname, outmsg); + } + } +} + +/*-----------------------------------------------------------------------*/ +/*QUAKED misc_ctf_banner (1 .5 0) (-4 -64 0) (4 64 248) TEAM2 +The origin is the bottom of the banner. +The banner is 248 tall. +*/ +static void misc_ctf_banner_think (edict_t *ent) +{ + ent->s.frame = (ent->s.frame + 1) % 16; + ent->nextthink = level.time + FRAMETIME; +} + +void SP_misc_ctf_banner (edict_t *ent) +{ + ent->movetype = MOVETYPE_NONE; + ent->solid = SOLID_NOT; + ent->s.modelindex = gi.modelindex ("models/ctf/banner/tris.md2"); + if (ent->spawnflags & 1) // team2 + ent->s.skinnum = 1; + + ent->s.frame = rand() % 16; + gi.linkentity (ent); + + ent->think = misc_ctf_banner_think; + ent->nextthink = level.time + FRAMETIME; +} + +/*QUAKED misc_ctf_small_banner (1 .5 0) (-4 -32 0) (4 32 124) TEAM2 +The origin is the bottom of the banner. +The banner is 124 tall. +*/ +void SP_misc_ctf_small_banner (edict_t *ent) +{ + ent->movetype = MOVETYPE_NONE; + ent->solid = SOLID_NOT; + ent->s.modelindex = gi.modelindex ("models/ctf/banner/small.md2"); + if (ent->spawnflags & 1) // team2 + ent->s.skinnum = 1; + + ent->s.frame = rand() % 16; + gi.linkentity (ent); + + ent->think = misc_ctf_banner_think; + ent->nextthink = level.time + FRAMETIME; +} + + +/*-----------------------------------------------------------------------*/ + +void CTFJoinTeam(edict_t *ent, int desired_team) +{ + char *s; + + PMenu_Close(ent); + + ent->svflags &= ~SVF_NOCLIENT; + ent->client->resp.ctf_team = desired_team; + ent->client->resp.ctf_state = CTF_STATE_START; + s = Info_ValueForKey (ent->client->pers.userinfo, "skin"); + CTFAssignSkin(ent, s); + +/* if(!hokuto->value) + {*/ + PutClientInServer (ent); + // add a teleportation effect + ent->s.event = EV_PLAYER_TELEPORT; + // hold in place briefly + ent->client->ps.pmove.pm_flags = PMF_TIME_TELEPORT; + ent->client->ps.pmove.pm_time = 14; + gi.bprintf(PRINT_HIGH, "%s joined the %s team.\n", + ent->client->pers.netname, CTFTeamName(ent->client->resp.ctf_team/*desired_team*/)); +/* } + else ZigockJoinMenu(ent);*/ +} + +void CTFJoinTeam1(edict_t *ent, pmenu_t *p) +{ + CTFJoinTeam(ent, CTF_TEAM1); +} + +void CTFJoinTeam2(edict_t *ent, pmenu_t *p) +{ + CTFJoinTeam(ent, CTF_TEAM2); +} + +void CTFChaseCam(edict_t *ent, pmenu_t *p) +{ + int i; + edict_t *e; + + if (ent->client->chase_target) { + ent->client->chase_target = NULL; + PMenu_Close(ent); + return; + } + + for (i = 1; i <= maxclients->value; i++) { + e = g_edicts + i; + if (e->inuse && e->solid != SOLID_NOT) { + ent->client->chase_target = e; + PMenu_Close(ent); + ent->client->update_chase = true; + break; + } + } +} + +void CTFReturnToMain(edict_t *ent, pmenu_t *p) +{ + PMenu_Close(ent); + CTFOpenJoinMenu(ent); +} + +void CTFCredits(edict_t *ent, pmenu_t *p); + +void DeathmatchScoreboard (edict_t *ent); + +void CTFShowScores(edict_t *ent, pmenu_t *p) +{ + PMenu_Close(ent); + + ent->client->showscores = true; + ent->client->showinventory = false; + DeathmatchScoreboard (ent); +} + +pmenu_t creditsmenu[] = { + { "*Quake II", PMENU_ALIGN_CENTER, NULL, NULL }, + { "*ThreeWave Capture the Flag", PMENU_ALIGN_CENTER, NULL, NULL }, + { NULL, PMENU_ALIGN_CENTER, NULL, NULL }, + { "*Programming", PMENU_ALIGN_CENTER, NULL, NULL }, + { "Dave 'Zoid' Kirsch", PMENU_ALIGN_CENTER, NULL, NULL }, + { "*Level Design", PMENU_ALIGN_CENTER, NULL, NULL }, + { "Christian Antkow", PMENU_ALIGN_CENTER, NULL, NULL }, + { "Tim Willits", PMENU_ALIGN_CENTER, NULL, NULL }, + { "Dave 'Zoid' Kirsch", PMENU_ALIGN_CENTER, NULL, NULL }, + { "*Art", PMENU_ALIGN_CENTER, NULL, NULL }, + { "Adrian Carmack Paul Steed", PMENU_ALIGN_CENTER, NULL, NULL }, + { "Kevin Cloud", PMENU_ALIGN_CENTER, NULL, NULL }, + { "*Sound", PMENU_ALIGN_CENTER, NULL, NULL }, + { "Tom 'Bjorn' Klok", PMENU_ALIGN_CENTER, NULL, NULL }, + { "*Original CTF Art Design", PMENU_ALIGN_CENTER, NULL, NULL }, + { "Brian 'Whaleboy' Cozzens", PMENU_ALIGN_CENTER, NULL, NULL }, + { NULL, PMENU_ALIGN_CENTER, NULL, NULL }, + { "Return to Main Menu", PMENU_ALIGN_LEFT, NULL, CTFReturnToMain } +}; + + +pmenu_t joinmenu[] = { + { "*Quake II", PMENU_ALIGN_CENTER, NULL, NULL }, + { "*ThreeWave Capture the Flag", PMENU_ALIGN_CENTER, NULL, NULL }, + { NULL, PMENU_ALIGN_CENTER, NULL, NULL }, + { NULL, PMENU_ALIGN_CENTER, NULL, NULL }, + { "Join Red Team", PMENU_ALIGN_LEFT, NULL, CTFJoinTeam1 }, + { NULL, PMENU_ALIGN_LEFT, NULL, NULL }, + { "Join Blue Team", PMENU_ALIGN_LEFT, NULL, CTFJoinTeam2 }, + { NULL, PMENU_ALIGN_LEFT, NULL, NULL }, + { "Chase Camera", PMENU_ALIGN_LEFT, NULL, CTFChaseCam }, + { "Credits", PMENU_ALIGN_LEFT, NULL, CTFCredits }, + { NULL, PMENU_ALIGN_LEFT, NULL, NULL }, + { "Use [ and ] to move cursor", PMENU_ALIGN_LEFT, NULL, NULL }, + { "ENTER to select", PMENU_ALIGN_LEFT, NULL, NULL }, + { "ESC to Exit Menu", PMENU_ALIGN_LEFT, NULL, NULL }, + { "(TAB to Return)", PMENU_ALIGN_LEFT, NULL, NULL }, + { NULL, PMENU_ALIGN_LEFT, NULL, NULL }, + { "v" CTF_STRING_VERSION, PMENU_ALIGN_RIGHT, NULL, NULL }, +}; + +int CTFUpdateJoinMenu(edict_t *ent) +{ + static char levelname[32]; + static char team1players[32]; + static char team2players[32]; + int num1, num2, i; + + joinmenu[4].text = "Join Red Team"; + joinmenu[4].SelectFunc = CTFJoinTeam1; + joinmenu[6].text = "Join Blue Team"; + joinmenu[6].SelectFunc = CTFJoinTeam2; + + if (ctf_forcejoin->string && *ctf_forcejoin->string) { + if (stricmp(ctf_forcejoin->string, "red") == 0) { + joinmenu[6].text = NULL; + joinmenu[6].SelectFunc = NULL; + } else if (stricmp(ctf_forcejoin->string, "blue") == 0) { + joinmenu[4].text = NULL; + joinmenu[4].SelectFunc = NULL; + } + } + + if (ent->client->chase_target) + joinmenu[8].text = "Leave Chase Camera"; + else + joinmenu[8].text = "Chase Camera"; + + levelname[0] = '*'; + if (g_edicts[0].message) + strncpy(levelname+1, g_edicts[0].message, sizeof(levelname) - 2); + else + strncpy(levelname+1, level.mapname, sizeof(levelname) - 2); + levelname[sizeof(levelname) - 1] = 0; + + num1 = num2 = 0; + for (i = 0; i < maxclients->value; i++) { + if (!g_edicts[i+1].inuse) + continue; + if (game.clients[i].resp.ctf_team == CTF_TEAM1) + num1++; + else if (game.clients[i].resp.ctf_team == CTF_TEAM2) + num2++; + } + + sprintf(team1players, " (%d players)", num1); + sprintf(team2players, " (%d players)", num2); + + joinmenu[2].text = levelname; + if (joinmenu[4].text) + joinmenu[5].text = team1players; + else + joinmenu[5].text = NULL; + if (joinmenu[6].text) + joinmenu[7].text = team2players; + else + joinmenu[7].text = NULL; + + if (num1 > num2) + return CTF_TEAM1; + else if (num2 > num1) + return CTF_TEAM1; + return (rand() & 1) ? CTF_TEAM1 : CTF_TEAM2; +} + +void CTFOpenJoinMenu(edict_t *ent) +{ + int team; + + team = CTFUpdateJoinMenu(ent); + if (ent->client->chase_target) + team = 8; + else if (team == CTF_TEAM1) + team = 4; + else + team = 6; + PMenu_Open(ent, joinmenu, team, sizeof(joinmenu) / sizeof(pmenu_t)); +} + +void CTFCredits(edict_t *ent, pmenu_t *p) +{ + PMenu_Close(ent); + PMenu_Open(ent, creditsmenu, -1, sizeof(creditsmenu) / sizeof(pmenu_t)); +} + +qboolean CTFStartClient(edict_t *ent) +{ + if (ent->client->resp.ctf_team != CTF_NOTEAM) + return false; + + if (!((int)dmflags->value & DF_CTF_FORCEJOIN)) { + // start as 'observer' + ent->movetype = MOVETYPE_NOCLIP; + ent->solid = SOLID_NOT; + ent->svflags |= SVF_NOCLIENT; + ent->client->resp.ctf_team = CTF_NOTEAM; + ent->client->ps.gunindex = 0; + gi.linkentity (ent); + + CTFOpenJoinMenu(ent); + return true; + } + return false; +} + +qboolean CTFCheckRules(void) +{ + if (capturelimit->value && + (ctfgame.team1 >= capturelimit->value || + ctfgame.team2 >= capturelimit->value)) { + gi.bprintf (PRINT_HIGH, "Capturelimit hit.\n"); + return true; + } + return false; +} + +/*-------------------------------------------------------------------------- + * just here to help old map conversions + *--------------------------------------------------------------------------*/ + +static void old_teleporter_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + edict_t *dest; + int i; + vec3_t forward; + + if (!other->client) + return; + dest = G_Find (NULL, FOFS(targetname), self->target); + if (!dest) + { + gi.dprintf ("Couldn't find destination\n"); + return; + } + +//ZOID + CTFPlayerResetGrapple(other); +//ZOID + + // unlink to make sure it can't possibly interfere with KillBox + gi.unlinkentity (other); + + VectorCopy (dest->s.origin, other->s.origin); + VectorCopy (dest->s.origin, other->s.old_origin); +// other->s.origin[2] += 10; + + // clear the velocity and hold them in place briefly + VectorClear (other->velocity); + other->client->ps.pmove.pm_time = 160>>3; // hold time + other->client->ps.pmove.pm_flags |= PMF_TIME_TELEPORT; + + // draw the teleport splash at source and on the player + self->enemy->s.event = EV_PLAYER_TELEPORT; + other->s.event = EV_PLAYER_TELEPORT; + + // set angles + for (i=0 ; i<3 ; i++) + other->client->ps.pmove.delta_angles[i] = ANGLE2SHORT(dest->s.angles[i] - other->client->resp.cmd_angles[i]); + + other->s.angles[PITCH] = 0; + other->s.angles[YAW] = dest->s.angles[YAW]; + other->s.angles[ROLL] = 0; + VectorCopy (dest->s.angles, other->client->ps.viewangles); + VectorCopy (dest->s.angles, other->client->v_angle); + + // give a little forward velocity + AngleVectors (other->client->v_angle, forward, NULL, NULL); + VectorScale(forward, 200, other->velocity); + + // kill anything at the destination + if (!KillBox (other)) + { + } + + gi.linkentity (other); +} + +/*QUAKED trigger_teleport (0.5 0.5 0.5) ? +Players touching this will be teleported +*/ +void SP_trigger_teleport (edict_t *ent) +{ + edict_t *s; + int i; + + if (!ent->target) + { + gi.dprintf ("teleporter without a target.\n"); + G_FreeEdict (ent); + return; + } + + ent->svflags |= SVF_NOCLIENT; + ent->solid = SOLID_TRIGGER; + ent->touch = old_teleporter_touch; + if(ent->model) + gi.setmodel (ent, ent->model); + gi.linkentity (ent); + + // noise maker and splash effect dude + s = G_Spawn(); + ent->enemy = s; + for (i = 0; i < 3; i++) + s->s.origin[i] = ent->mins[i] + (ent->maxs[i] - ent->mins[i])/2; + s->s.sound = gi.soundindex ("world/hum1.wav"); + gi.linkentity(s); + +} + +/*QUAKED info_teleport_destination (0.5 0.5 0.5) (-16 -16 -24) (16 16 32) +Point trigger_teleports at these. +*/ +void SP_info_teleport_destination (edict_t *ent) +{ + ent->s.origin[2] += 16; +} + +//PON +void SpawnExtra(vec3_t position,char *classname); +void ED_CallSpawn (edict_t *ent); + +//------------------------ +// +// Route setup +// +// Not only for CTF but also DM +// +//------------------------ +void CTFSetupNavSpawn() +{ + FILE *fpout; + char name[256]; + char code[8]; + char SRCcode[8]; + int i,j; + vec3_t v; + edict_t *other; + + unsigned int size; + +//PONKO + spawncycle = level.time + FRAMETIME * 100; +//PONKO + //ルート初期化 + CurrentIndex = 0; + memset(Route,0,sizeof(Route)); + memset(code,0,8); + + if(!ctf->value) sprintf(name,".\\%s\\chdtm\\%s.chn",gamepath->string,level.mapname); + else sprintf(name,".\\%s\\chctf\\%s.chf",gamepath->string,level.mapname); + + fpout = fopen(name,"rb"); + if(fpout == NULL) + { + if(!ctf->value) gi.dprintf("Chaining: file %s.chn not found.\n",level.mapname); + else gi.dprintf("Chaining: file %s.chf not found.\n",level.mapname); + } + else + { + fread(code,sizeof(char),8,fpout); + + if(!ctf->value) strncpy(SRCcode,"3ZBRGDTM",8); + else strncpy(SRCcode,"3ZBRGCTF",8); + + if(strncmp(code,SRCcode,8)) + { + CurrentIndex = 0; + gi.dprintf("Chaining: %s.chn is not a chaining file.\n",level.mapname); + fclose(fpout); + return; + } + gi.dprintf("Chaining: %s.chn founded.\n",level.mapname); + fread(&CurrentIndex,sizeof(int),1,fpout); + + size = (unsigned int)CurrentIndex * sizeof(route_t); + fread(Route,size,1,fpout); + + for(i = 0;i < CurrentIndex;i++) + { +if(Route[i].state == GRS_TELEPORT) +gi.dprintf("GRS_TELEPORT\n"); + if((Route[i].state > GRS_TELEPORT/*GRS_ONROTATE*/ && Route[i].state <= GRS_PUSHBUTTON) + || Route[i].state == GRS_REDFLAG || Route[i].state == GRS_BLUEFLAG) + { + other = &g_edicts[(int)maxclients->value+1]; + for ( j=maxclients->value+1 ; jinuse) + { + if(Route[i].state == GRS_ONPLAT + || Route[i].state == GRS_ONTRAIN + || Route[i].state == GRS_PUSHBUTTON + || Route[i].state == GRS_ONDOOR) + { + VectorAdd(other->s.origin,other->mins,v); + if(VectorCompare (Route[i].Pt,v/*other->monsterinfo.last_sighting*/)) + { + //onplat + if(Route[i].state == GRS_ONPLAT + && !Q_stricmp(other->classname, "func_plat")) + { +//gi.dprintf("assingned %s\n",other->classname); + Route[i].ent = other; + break; + } + //train + else if(Route[i].state == GRS_ONTRAIN + && !Q_stricmp(other->classname, "func_train")) + { +//gi.dprintf("assingned %s\n",other->classname); + Route[i].ent = other; + break; + } + //button + else if(Route[i].state == GRS_PUSHBUTTON + && !Q_stricmp(other->classname, "func_button")) + { +//gi.dprintf("assingned %s\n",other->classname); + Route[i].ent = other; + break; + } + //door + else if(Route[i].state == GRS_ONDOOR + && !Q_stricmp(other->classname, "func_door")) + { +//gi.dprintf("assingned %s\n",other->classname); + Route[i].ent = other; + break; + } + } + } + else if(Route[i].state == GRS_ITEMS + || Route[i].state == GRS_REDFLAG || Route[i].state == GRS_BLUEFLAG) + { +//else gi.dprintf("CYAU!!!!!!!\n"); + if(VectorCompare (Route[i].Pt,other->monsterinfo.last_sighting/*->s.origin*/)) + { + //onplat + if(1/*other->classname[0] == 'w' || other->classname[0] == 'i'*/) + { +//if(Route[i].state == GRS_REDFLAG || Route[i].state == GRS_BLUEFLAG) +//gi.dprintf("HATA ATTA!!!!!!!\n"); +//gi.dprintf("assingned %s\n",other->classname); + Route[i].ent = other; + break; + } + } + else + { +//if(Route[i].state == GRS_REDFLAG || Route[i].state == GRS_BLUEFLAG) +//gi.dprintf("HATA Dame!!!!!!!\n"); + } + } + } + } + if(j >= globals.num_edicts && (Route[i].state == GRS_ITEMS || Route[i].state == GRS_REDFLAG || Route[i].state == GRS_BLUEFLAG)) gi.dprintf("kicked item\n"); + if(j >= globals.num_edicts) Route[i].state = GRS_NORMAL; + } + } + gi.dprintf("Chaining: Total %i chaining pod assigned.\n",CurrentIndex); + fclose(fpout); + } + return; +} + +void SpawnExtra(vec3_t position,char *classname) +{ + edict_t *it_ent; + + it_ent = G_Spawn(); + + it_ent->classname = classname; + VectorCopy(position,it_ent->s.origin); + ED_CallSpawn(it_ent); + + if(ctf->value && chedit->value) + { + it_ent->moveinfo.speed = -1; + it_ent->s.effects |= EF_QUAD; + } +} + +void CTFJobAssign (void) +{ + int i; + int defend1,defend2; //ディフェンダー総数 + int mate1,mate2; //チームメイト総数 + gclient_t *client; + edict_t *e; + edict_t *defei1,*defei2; //候補 + edict_t *geti1,*geti2; //候補 + + defend1 = 0; + defend2 = 0; + mate1 = 0; + mate2 = 0; + defei1 = NULL; + defei2 = NULL; + geti1 = NULL; + geti2 = NULL; + + e = &g_edicts[(int)maxclients->value]; + for ( i = maxclients->value ; i >= 1 ; i--, e--) + { + if (e->inuse) + { + client = e->client; + if(client->zc.ctfstate == CTFS_NONE) client->zc.ctfstate = CTFS_DEFENDER; +//if(client->zc.ctfstate == CTFS_CARRIER) +//gi.bprintf(PRINT_HIGH,"I am carrierY!!\n"); + if(e->client->resp.ctf_team == CTF_TEAM1) + { + mate1++; + if( e->client->pers.inventory[ITEM_INDEX(FindItem("Blue Flag"))]) + { + client->zc.ctfstate = CTFS_CARRIER; + } + if(1/*e->svflags & SVF_MONSTER*/) + { + + if( client->zc.ctfstate == CTFS_OFFENCER && random()>0.7) defei1 = e; + else if( client->zc.ctfstate == CTFS_DEFENDER) + { + if(random()>0.7) geti1 = e; + defend1++; + } + else if( client->zc.ctfstate == CTFS_CARRIER ) defend1++; + } + } + else if(e->client->resp.ctf_team == CTF_TEAM2) + { + mate2++; + if( e->client->pers.inventory[ITEM_INDEX(FindItem("Red Flag"))]) + { + client->zc.ctfstate = CTFS_CARRIER; + } + if(1/*e->svflags & SVF_MONSTER*/) + { + if( client->zc.ctfstate == CTFS_OFFENCER && random()>0.8) defei2 = e; + else if( client->zc.ctfstate == CTFS_DEFENDER) + { + if(random()>0.7) geti2 = e; + defend2++; + } + else if( client->zc.ctfstate == CTFS_CARRIER ) defend2++; + } + } + } + } + + if(defend1 < mate1 / 3 && mate1 >= 2) + { + if(defei1 != NULL) defei1->client->zc.ctfstate = CTFS_DEFENDER; + } + else if(defend1 > mate1 / 3 ) + { + if(geti1 != NULL) geti1->client->zc.ctfstate = CTFS_OFFENCER; + } + if(defend2 < mate2 / 3 && mate2 >= 2) + { + if(defei2 != NULL) defei2->client->zc.ctfstate = CTFS_DEFENDER; + } + else if(defend2 > mate2 / 3 ) + { + if(geti2 != NULL) geti2->client->zc.ctfstate = CTFS_OFFENCER; + } +/// gi.bprintf(PRINT_HIGH,"Called!!!!\n"); +} \ No newline at end of file diff --git a/src/g_ctf.h b/src/g_ctf.h new file mode 100644 index 0000000..9400969 --- /dev/null +++ b/src/g_ctf.h @@ -0,0 +1,136 @@ +#ifndef _CTF +#define _CTF + +#define CTF_VERSION 1.02 +#define CTF_VSTRING2(x) #x +#define CTF_VSTRING(x) CTF_VSTRING2(x) +#define CTF_STRING_VERSION CTF_VSTRING(CTF_VERSION) + +#define STAT_CTF_TEAM1_PIC 17 +#define STAT_CTF_TEAM1_CAPS 18 +#define STAT_CTF_TEAM2_PIC 19 +#define STAT_CTF_TEAM2_CAPS 20 +#define STAT_CTF_FLAG_PIC 21 +#define STAT_CTF_JOINED_TEAM1_PIC 22 +#define STAT_CTF_JOINED_TEAM2_PIC 23 +#define STAT_CTF_TEAM1_HEADER 24 +#define STAT_CTF_TEAM2_HEADER 25 +#define STAT_CTF_TECH 26 +#define STAT_CTF_ID_VIEW 27 + +typedef enum { + CTF_NOTEAM, + CTF_TEAM1, + CTF_TEAM2 +} ctfteam_t; + +typedef enum { + CTF_STATE_START, + CTF_STATE_PLAYING +} ctfstate_t; + +typedef enum { + CTF_GRAPPLE_STATE_FLY, + CTF_GRAPPLE_STATE_PULL, + CTF_GRAPPLE_STATE_HANG +} ctfgrapplestate_t; + +extern cvar_t *ctf; + +#define CTF_TEAM1_SKIN "ctf_r" +#define CTF_TEAM2_SKIN "ctf_b" + +#define DF_CTF_FORCEJOIN 131072 +#define DF_ARMOR_PROTECT 262144 +#define DF_CTF_NO_TECH 524288 + +#define CTF_CAPTURE_BONUS 15 // what you get for capture +#define CTF_TEAM_BONUS 10 // what your team gets for capture +#define CTF_RECOVERY_BONUS 1 // what you get for recovery +#define CTF_FLAG_BONUS 0 // what you get for picking up enemy flag +#define CTF_FRAG_CARRIER_BONUS 2 // what you get for fragging enemy flag carrier +#define CTF_FLAG_RETURN_TIME 40 // seconds until auto return + +#define CTF_CARRIER_DANGER_PROTECT_BONUS 2 // bonus for fraggin someone who has recently hurt your flag carrier +#define CTF_CARRIER_PROTECT_BONUS 1 // bonus for fraggin someone while either you or your target are near your flag carrier +#define CTF_FLAG_DEFENSE_BONUS 1 // bonus for fraggin someone while either you or your target are near your flag +#define CTF_RETURN_FLAG_ASSIST_BONUS 1 // awarded for returning a flag that causes a capture to happen almost immediately +#define CTF_FRAG_CARRIER_ASSIST_BONUS 2 // award for fragging a flag carrier if a capture happens almost immediately + +#define CTF_TARGET_PROTECT_RADIUS 400 // the radius around an object being defended where a target will be worth extra frags +#define CTF_ATTACKER_PROTECT_RADIUS 400 // the radius around an object being defended where an attacker will get extra frags when making kills + +#define CTF_CARRIER_DANGER_PROTECT_TIMEOUT 8 +#define CTF_FRAG_CARRIER_ASSIST_TIMEOUT 10 +#define CTF_RETURN_FLAG_ASSIST_TIMEOUT 10 + +#define CTF_AUTO_FLAG_RETURN_TIMEOUT 30 // number of seconds before dropped flag auto-returns + +#define CTF_TECH_TIMEOUT 60 // seconds before techs spawn again + +#define CTF_GRAPPLE_SPEED 650 // speed of grapple in flight +#define CTF_GRAPPLE_PULL_SPEED 650 // speed player is pulled at + +void CTFInit(void); + +void SP_info_player_team1(edict_t *self); +void SP_info_player_team2(edict_t *self); + +char *CTFTeamName(int team); +char *CTFOtherTeamName(int team); +void CTFAssignSkin(edict_t *ent, char *s); +void CTFAssignTeam(gclient_t *who); +edict_t *SelectCTFSpawnPoint (edict_t *ent); +qboolean CTFPickup_Flag(edict_t *ent, edict_t *other); +qboolean CTFDrop_Flag(edict_t *ent, gitem_t *item); +void CTFEffects(edict_t *player); +void CTFCalcScores(void); +void SetCTFStats(edict_t *ent); +void CTFDeadDropFlag(edict_t *self); +void CTFScoreboardMessage (edict_t *ent, edict_t *killer); +void CTFTeam_f (edict_t *ent); +void CTFID_f (edict_t *ent); +void CTFSay_Team(edict_t *who, char *msg); +void CTFFlagSetup (edict_t *ent); +void CTFResetFlag(int ctf_team); +void CTFFragBonuses(edict_t *targ, edict_t *inflictor, edict_t *attacker); +void CTFCheckHurtCarrier(edict_t *targ, edict_t *attacker); + +// GRAPPLE +void CTFWeapon_Grapple (edict_t *ent); +void CTFPlayerResetGrapple(edict_t *ent); +void CTFGrapplePull(edict_t *self); +void CTFResetGrapple(edict_t *self); + +//TECH +gitem_t *CTFWhat_Tech(edict_t *ent); +qboolean CTFPickup_Tech (edict_t *ent, edict_t *other); +void CTFDrop_Tech(edict_t *ent, gitem_t *item); +void CTFDeadDropTech(edict_t *ent); +void CTFSetupTechSpawn(void); +int CTFApplyResistance(edict_t *ent, int dmg); +int CTFApplyStrength(edict_t *ent, int dmg); +qboolean CTFApplyStrengthSound(edict_t *ent); +qboolean CTFApplyHaste(edict_t *ent); +void CTFApplyHasteSound(edict_t *ent); +void CTFApplyRegeneration(edict_t *ent); +qboolean CTFHasRegeneration(edict_t *ent); +void CTFRespawnTech(edict_t *ent); + +void CTFOpenJoinMenu(edict_t *ent); +qboolean CTFStartClient(edict_t *ent); + +qboolean CTFCheckRules(void); + +void SP_misc_ctf_banner (edict_t *ent); +void SP_misc_ctf_small_banner (edict_t *ent); + +extern char *ctf_statusbar; + +void UpdateChaseCam(edict_t *ent); +void ChaseNext(edict_t *ent); +void ChasePrev(edict_t *ent); + +void SP_trigger_teleport (edict_t *ent); +void SP_info_teleport_destination (edict_t *ent); +#endif \ No newline at end of file diff --git a/src/g_func.c b/src/g_func.c new file mode 100644 index 0000000..8b49645 --- /dev/null +++ b/src/g_func.c @@ -0,0 +1,2397 @@ +#include "g_local.h" +#include "bot.h" + +void SpawnItem3 (edict_t *ent, gitem_t *item); +/* +========================================================= + + PLATS + + movement options: + + linear + smooth start, hard stop + smooth start, smooth stop + + start + end + acceleration + speed + deceleration + begin sound + end sound + target fired when reaching end + wait at end + + object characteristics that use move segments + --------------------------------------------- + movetype_push, or movetype_stop + action when touched + action when blocked + action when used + disabled? + auto trigger spawning + + +========================================================= +*/ + +#define PLAT_LOW_TRIGGER 1 + +#define STATE_TOP 0 +#define STATE_BOTTOM 1 +#define STATE_UP 2 +#define STATE_DOWN 3 + +#define DOOR_START_OPEN 1 +#define DOOR_REVERSE 2 +#define DOOR_CRUSHER 4 +#define DOOR_NOMONSTER 8 +#define DOOR_TOGGLE 32 +#define DOOR_X_AXIS 64 +#define DOOR_Y_AXIS 128 + + +// +// Support routines for movement (changes in origin using velocity) +// + +void Move_Done (edict_t *ent) +{ + VectorClear (ent->velocity); + ent->moveinfo.endfunc (ent); +} + +void Move_Final (edict_t *ent) +{ + if (ent->moveinfo.remaining_distance == 0) + { + Move_Done (ent); + return; + } + + VectorScale (ent->moveinfo.dir, ent->moveinfo.remaining_distance / FRAMETIME, ent->velocity); + + ent->think = Move_Done; + ent->nextthink = level.time + FRAMETIME; +} + +void Move_Begin (edict_t *ent) +{ + float frames; + + if ((ent->moveinfo.speed * FRAMETIME) >= ent->moveinfo.remaining_distance) + { + Move_Final (ent); + return; + } + VectorScale (ent->moveinfo.dir, ent->moveinfo.speed, ent->velocity); + frames = floor((ent->moveinfo.remaining_distance / ent->moveinfo.speed) / FRAMETIME); + ent->moveinfo.remaining_distance -= frames * ent->moveinfo.speed * FRAMETIME; + ent->nextthink = level.time + (frames * FRAMETIME); + ent->think = Move_Final; +} + +void Think_AccelMove (edict_t *ent); + +void Move_Calc (edict_t *ent, vec3_t dest, void(*func)(edict_t*)) +{ + VectorClear (ent->velocity); + VectorSubtract (dest, ent->s.origin, ent->moveinfo.dir); + ent->moveinfo.remaining_distance = VectorNormalize (ent->moveinfo.dir); + ent->moveinfo.endfunc = func; + + if (ent->moveinfo.speed == ent->moveinfo.accel && ent->moveinfo.speed == ent->moveinfo.decel) + { + if (level.current_entity == ((ent->flags & FL_TEAMSLAVE) ? ent->teammaster : ent)) + { + Move_Begin (ent); + } + else + { + ent->nextthink = level.time + FRAMETIME; + ent->think = Move_Begin; + } + } + else + { + // accelerative + ent->moveinfo.current_speed = 0; + ent->think = Think_AccelMove; + ent->nextthink = level.time + FRAMETIME; + } +} + + +// +// Support routines for angular movement (changes in angle using avelocity) +// + +void AngleMove_Done (edict_t *ent) +{ + VectorClear (ent->avelocity); + ent->moveinfo.endfunc (ent); +} + +void AngleMove_Final (edict_t *ent) +{ + vec3_t move; + + if (ent->moveinfo.state == STATE_UP) + VectorSubtract (ent->moveinfo.end_angles, ent->s.angles, move); + else + VectorSubtract (ent->moveinfo.start_angles, ent->s.angles, move); + + if (VectorCompare (move, vec3_origin)) + { + AngleMove_Done (ent); + return; + } + + VectorScale (move, 1.0/FRAMETIME, ent->avelocity); + + ent->think = AngleMove_Done; + ent->nextthink = level.time + FRAMETIME; +} + +void AngleMove_Begin (edict_t *ent) +{ + vec3_t destdelta; + float len; + float traveltime; + float frames; + + // set destdelta to the vector needed to move + if (ent->moveinfo.state == STATE_UP) + VectorSubtract (ent->moveinfo.end_angles, ent->s.angles, destdelta); + else + VectorSubtract (ent->moveinfo.start_angles, ent->s.angles, destdelta); + + // calculate length of vector + len = VectorLength (destdelta); + + // divide by speed to get time to reach dest + traveltime = len / ent->moveinfo.speed; + + if (traveltime < FRAMETIME) + { + AngleMove_Final (ent); + return; + } + + frames = floor(traveltime / FRAMETIME); + + // scale the destdelta vector by the time spent traveling to get velocity + VectorScale (destdelta, 1.0 / traveltime, ent->avelocity); + + // set nextthink to trigger a think when dest is reached + ent->nextthink = level.time + frames * FRAMETIME; + ent->think = AngleMove_Final; +} + +void AngleMove_Calc (edict_t *ent, void(*func)(edict_t*)) +{ + VectorClear (ent->avelocity); + ent->moveinfo.endfunc = func; + if (level.current_entity == ((ent->flags & FL_TEAMSLAVE) ? ent->teammaster : ent)) + { + AngleMove_Begin (ent); + } + else + { + ent->nextthink = level.time + FRAMETIME; + ent->think = AngleMove_Begin; + } +} + + +/* +============== +Think_AccelMove + +The team has completed a frame of movement, so +change the speed for the next frame +============== +*/ +#define AccelerationDistance(target, rate) (target * ((target / rate) + 1) / 2) + +void plat_CalcAcceleratedMove(moveinfo_t *moveinfo) +{ + float accel_dist; + float decel_dist; + + moveinfo->move_speed = moveinfo->speed; + + if (moveinfo->remaining_distance < moveinfo->accel) + { + moveinfo->current_speed = moveinfo->remaining_distance; + return; + } + + accel_dist = AccelerationDistance (moveinfo->speed, moveinfo->accel); + decel_dist = AccelerationDistance (moveinfo->speed, moveinfo->decel); + + if ((moveinfo->remaining_distance - accel_dist - decel_dist) < 0) + { + float f; + + f = (moveinfo->accel + moveinfo->decel) / (moveinfo->accel * moveinfo->decel); + moveinfo->move_speed = (-2 + sqrt(4 - 4 * f * (-2 * moveinfo->remaining_distance))) / (2 * f); + decel_dist = AccelerationDistance (moveinfo->move_speed, moveinfo->decel); + } + + moveinfo->decel_distance = decel_dist; +}; + +void plat_Accelerate (moveinfo_t *moveinfo) +{ + // are we decelerating? + if (moveinfo->remaining_distance <= moveinfo->decel_distance) + { + if (moveinfo->remaining_distance < moveinfo->decel_distance) + { + if (moveinfo->next_speed) + { + moveinfo->current_speed = moveinfo->next_speed; + moveinfo->next_speed = 0; + return; + } + if (moveinfo->current_speed > moveinfo->decel) + moveinfo->current_speed -= moveinfo->decel; + } + return; + } + + // are we at full speed and need to start decelerating during this move? + if (moveinfo->current_speed == moveinfo->move_speed) + if ((moveinfo->remaining_distance - moveinfo->current_speed) < moveinfo->decel_distance) + { + float p1_distance; + float p2_distance; + float distance; + + p1_distance = moveinfo->remaining_distance - moveinfo->decel_distance; + p2_distance = moveinfo->move_speed * (1.0 - (p1_distance / moveinfo->move_speed)); + distance = p1_distance + p2_distance; + moveinfo->current_speed = moveinfo->move_speed; + moveinfo->next_speed = moveinfo->move_speed - moveinfo->decel * (p2_distance / distance); + return; + } + + // are we accelerating? + if (moveinfo->current_speed < moveinfo->speed) + { + float old_speed; + float p1_distance; + float p1_speed; + float p2_distance; + float distance; + + old_speed = moveinfo->current_speed; + + // figure simple acceleration up to move_speed + moveinfo->current_speed += moveinfo->accel; + if (moveinfo->current_speed > moveinfo->speed) + moveinfo->current_speed = moveinfo->speed; + + // are we accelerating throughout this entire move? + if ((moveinfo->remaining_distance - moveinfo->current_speed) >= moveinfo->decel_distance) + return; + + // during this move we will accelrate from current_speed to move_speed + // and cross over the decel_distance; figure the average speed for the + // entire move + p1_distance = moveinfo->remaining_distance - moveinfo->decel_distance; + p1_speed = (old_speed + moveinfo->move_speed) / 2.0; + p2_distance = moveinfo->move_speed * (1.0 - (p1_distance / p1_speed)); + distance = p1_distance + p2_distance; + moveinfo->current_speed = (p1_speed * (p1_distance / distance)) + (moveinfo->move_speed * (p2_distance / distance)); + moveinfo->next_speed = moveinfo->move_speed - moveinfo->decel * (p2_distance / distance); + return; + } + + // we are at constant velocity (move_speed) + return; +}; + +void Think_AccelMove (edict_t *ent) +{ + ent->moveinfo.remaining_distance -= ent->moveinfo.current_speed; + + if (ent->moveinfo.current_speed == 0) // starting or blocked + plat_CalcAcceleratedMove(&ent->moveinfo); + + plat_Accelerate (&ent->moveinfo); + + // will the entire move complete on next frame? + if (ent->moveinfo.remaining_distance <= ent->moveinfo.current_speed) + { + Move_Final (ent); + return; + } + + VectorScale (ent->moveinfo.dir, ent->moveinfo.current_speed*10, ent->velocity); + ent->nextthink = level.time + FRAMETIME; + ent->think = Think_AccelMove; +} + + +void plat_go_down (edict_t *ent); + +void plat_hit_top (edict_t *ent) +{ + if (!(ent->flags & FL_TEAMSLAVE)) + { + if (ent->moveinfo.sound_end) + gi.sound (ent, CHAN_NO_PHS_ADD+CHAN_VOICE, ent->moveinfo.sound_end, 1, ATTN_STATIC, 0); + ent->s.sound = 0; + } + ent->moveinfo.state = STATE_TOP; + + ent->think = plat_go_down; + ent->nextthink = level.time + 3; +} + +void plat_hit_bottom (edict_t *ent) +{ + if (!(ent->flags & FL_TEAMSLAVE)) + { + if (ent->moveinfo.sound_end) + gi.sound (ent, CHAN_NO_PHS_ADD+CHAN_VOICE, ent->moveinfo.sound_end, 1, ATTN_STATIC, 0); + ent->s.sound = 0; + } + ent->moveinfo.state = STATE_BOTTOM; +} + +void plat_go_down (edict_t *ent) +{ + if (!(ent->flags & FL_TEAMSLAVE)) + { + if (ent->moveinfo.sound_start) + gi.sound (ent, CHAN_NO_PHS_ADD+CHAN_VOICE, ent->moveinfo.sound_start, 1, ATTN_STATIC, 0); + ent->s.sound = ent->moveinfo.sound_middle; + } + ent->moveinfo.state = STATE_DOWN; + Move_Calc (ent, ent->moveinfo.end_origin, plat_hit_bottom); +} + +void plat_go_up (edict_t *ent) +{ + if (!(ent->flags & FL_TEAMSLAVE)) + { + if (ent->moveinfo.sound_start) + gi.sound (ent, CHAN_NO_PHS_ADD+CHAN_VOICE, ent->moveinfo.sound_start, 1, ATTN_STATIC, 0); + ent->s.sound = ent->moveinfo.sound_middle; + } + ent->moveinfo.state = STATE_UP; + Move_Calc (ent, ent->moveinfo.start_origin, plat_hit_top); +} + +void plat_blocked (edict_t *self, edict_t *other) +{ + if (!(other->svflags & SVF_MONSTER) && (!other->client)) + { + // give it a chance to go away on it's own terms (like gibs) + T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, 100000, 1, 0, MOD_CRUSH); + // if it's still there, nuke it + if (other) + BecomeExplosion1 (other); + return; + } + + if(other->deadflag) T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, 100000, 1, 0, MOD_CRUSH); + else T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, self->dmg, 1, 0, MOD_CRUSH); + + if (self->moveinfo.state == STATE_UP) + plat_go_down (self); + else if (self->moveinfo.state == STATE_DOWN) + plat_go_up (self); +} + + +void Use_Plat (edict_t *ent, edict_t *other, edict_t *activator) +{ + if (ent->think) + return; // already down + plat_go_down (ent); +} + + +void Touch_Plat_Center (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + if (!other->client) + return; + + if (other->health <= 0) + return; + + ent = ent->enemy; // now point at the plat, not the trigger + if (ent->moveinfo.state == STATE_BOTTOM) + plat_go_up (ent); + else if (ent->moveinfo.state == STATE_TOP) + ent->nextthink = level.time + 1; // the player is still on the plat, so delay going down +} + +void plat_spawn_inside_trigger (edict_t *ent) +{ + edict_t *trigger; + vec3_t tmin, tmax; + +// +// middle trigger +// + trigger = G_Spawn(); + trigger->touch = Touch_Plat_Center; + trigger->movetype = MOVETYPE_NONE; + trigger->solid = SOLID_TRIGGER; + trigger->enemy = ent; + + tmin[0] = ent->mins[0] + 25; + tmin[1] = ent->mins[1] + 25; + tmin[2] = ent->mins[2]; + + tmax[0] = ent->maxs[0] - 25; + tmax[1] = ent->maxs[1] - 25; + tmax[2] = ent->maxs[2] + 8; + + tmin[2] = tmax[2] - (ent->pos1[2] - ent->pos2[2] + st.lip); + + if (ent->spawnflags & PLAT_LOW_TRIGGER) + tmax[2] = tmin[2] + 8; + + if (tmax[0] - tmin[0] <= 0) + { + tmin[0] = (ent->mins[0] + ent->maxs[0]) *0.5; + tmax[0] = tmin[0] + 1; + } + if (tmax[1] - tmin[1] <= 0) + { + tmin[1] = (ent->mins[1] + ent->maxs[1]) *0.5; + tmax[1] = tmin[1] + 1; + } + + VectorCopy (tmin, trigger->mins); + VectorCopy (tmax, trigger->maxs); + + gi.linkentity (trigger); +} + + +/*QUAKED func_plat (0 .5 .8) ? PLAT_LOW_TRIGGER +speed default 150 + +Plats are always drawn in the extended position, so they will light correctly. + +If the plat is the target of another trigger or button, it will start out disabled in the extended position until it is trigger, when it will lower and become a normal plat. + +"speed" overrides default 200. +"accel" overrides default 500 +"lip" overrides default 8 pixel lip + +If the "height" key is set, that will determine the amount the plat moves, instead of being implicitly determoveinfoned by the model's height. + +Set "sounds" to one of the following: +1) base fast +2) chain slow +*/ +void SP_func_plat (edict_t *ent) +{ + gitem_t *it; //j + edict_t *it_ent; //j + + VectorClear (ent->s.angles); + ent->solid = SOLID_BSP; + ent->movetype = MOVETYPE_PUSH; + + gi.setmodel (ent, ent->model); + + ent->blocked = plat_blocked; + + if (!ent->speed) + ent->speed = 20; + else + ent->speed *= 0.1; + + if (!ent->accel) + ent->accel = 5; + else + ent->accel *= 0.1; + + if (!ent->decel) + ent->decel = 5; + else + ent->decel *= 0.1; + + if (!ent->dmg) + ent->dmg = 2; + + if (!st.lip) + st.lip = 8; + + // pos1 is the top position, pos2 is the bottom + VectorCopy (ent->s.origin, ent->pos1); + VectorCopy (ent->s.origin, ent->pos2); + if (st.height) + ent->pos2[2] -= st.height; + else + ent->pos2[2] -= (ent->maxs[2] - ent->mins[2]) - st.lip; + + ent->use = Use_Plat; + + plat_spawn_inside_trigger (ent); // the "start moving" trigger + + if (ent->targetname) + { + ent->moveinfo.state = STATE_UP; + } + else + { + VectorCopy (ent->pos2, ent->s.origin); + gi.linkentity (ent); + ent->moveinfo.state = STATE_BOTTOM; + } + + ent->moveinfo.speed = ent->speed; + ent->moveinfo.accel = ent->accel; + ent->moveinfo.decel = ent->decel; + ent->moveinfo.wait = ent->wait; + VectorCopy (ent->pos1, ent->moveinfo.start_origin); + VectorCopy (ent->s.angles, ent->moveinfo.start_angles); + VectorCopy (ent->pos2, ent->moveinfo.end_origin); + VectorCopy (ent->s.angles, ent->moveinfo.end_angles); + + ent->moveinfo.sound_start = gi.soundindex ("plats/pt1_strt.wav"); + ent->moveinfo.sound_middle = gi.soundindex ("plats/pt1_mid.wav"); + ent->moveinfo.sound_end = gi.soundindex ("plats/pt1_end.wav"); + + +//////// + VectorAdd(ent->s.origin,ent->mins,ent->monsterinfo.last_sighting); +// VectorCopy(ent->s.origin,ent->monsterinfo.last_sighting); + + + it = FindItem("Roam Navi"); + it_ent = G_Spawn(); + it_ent->classname = it->classname; +/* VectorCopy (ent->s.origin,it_ent->s.origin); + it_ent->s.origin[0] = (ent->s.origin[0] + ent->mins[0] + ent->s.origin[0] + ent->maxs[0])/2; + it_ent->s.origin[1] = (ent->s.origin[1] + ent->mins[1] + ent->s.origin[1] + ent->maxs[1])/2; + it_ent->s.origin[2] = 0;*/ + + it_ent->union_ent = ent; + ent->union_ent = it_ent; + + SpawnItem3 (it_ent, it); +} + +//==================================================================== + +/*QUAKED func_rotating (0 .5 .8) ? START_ON REVERSE X_AXIS Y_AXIS TOUCH_PAIN STOP ANIMATED ANIMATED_FAST +You need to have an origin brush as part of this entity. The center of that brush will be +the point around which it is rotated. It will rotate around the Z axis by default. You can +check either the X_AXIS or Y_AXIS box to change that. + +"speed" determines how fast it moves; default value is 100. +"dmg" damage to inflict when blocked (2 default) + +REVERSE will cause the it to rotate in the opposite direction. +STOP mean it will stop moving instead of pushing entities +*/ + +void rotating_blocked (edict_t *self, edict_t *other) +{ + T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, self->dmg, 1, 0, MOD_CRUSH); +} + +void rotating_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + //ponko + if(other->svflags & SVF_MONSTER) return; + + if (self->avelocity[0] || self->avelocity[1] || self->avelocity[2]) + T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, self->dmg, 1, 0, MOD_CRUSH); +} + +void rotating_use (edict_t *self, edict_t *other, edict_t *activator) +{ + if (!VectorCompare (self->avelocity, vec3_origin)) + { + self->s.sound = 0; + VectorClear (self->avelocity); + self->touch = NULL; + } + else + { + self->s.sound = self->moveinfo.sound_middle; + VectorScale (self->movedir, self->speed, self->avelocity); + if (self->spawnflags & 16) + self->touch = rotating_touch; + } +} + +void SP_func_rotating (edict_t *ent) +{ + ent->solid = SOLID_BSP; + if (ent->spawnflags & 32) + ent->movetype = MOVETYPE_STOP; + else + ent->movetype = MOVETYPE_PUSH; + + // set the axis of rotation + VectorClear(ent->movedir); + if (ent->spawnflags & 4) + ent->movedir[2] = 1.0; + else if (ent->spawnflags & 8) + ent->movedir[0] = 1.0; + else // Z_AXIS + ent->movedir[1] = 1.0; + + // check for reverse rotation + if (ent->spawnflags & 2) + VectorNegate (ent->movedir, ent->movedir); + + if (!ent->speed) + ent->speed = 100; + if (!ent->dmg) + ent->dmg = 2; + +// ent->moveinfo.sound_middle = "doors/hydro1.wav"; + + ent->use = rotating_use; + if (ent->dmg) + ent->blocked = rotating_blocked; + + if (ent->spawnflags & 1) + ent->use (ent, NULL, NULL); + + if (ent->spawnflags & 64) + ent->s.effects |= EF_ANIM_ALL; + if (ent->spawnflags & 128) + ent->s.effects |= EF_ANIM_ALLFAST; + + gi.setmodel (ent, ent->model); + gi.linkentity (ent); +} + +/* +====================================================================== + +BUTTONS + +====================================================================== +*/ + +/*QUAKED func_button (0 .5 .8) ? +When a button is touched, it moves some distance in the direction of it's angle, triggers all of it's targets, waits some time, then returns to it's original position where it can be triggered again. + +"angle" determines the opening direction +"target" all entities with a matching targetname will be used +"speed" override the default 40 speed +"wait" override the default 1 second wait (-1 = never return) +"lip" override the default 4 pixel lip remaining at end of move +"health" if set, the button must be killed instead of touched +"sounds" +1) silent +2) steam metal +3) wooden clunk +4) metallic click +5) in-out +*/ + +void button_done (edict_t *self) +{ + self->moveinfo.state = STATE_BOTTOM; + self->s.effects &= ~EF_ANIM23; + self->s.effects |= EF_ANIM01; +} + +void button_return (edict_t *self) +{ + self->moveinfo.state = STATE_DOWN; + + Move_Calc (self, self->moveinfo.start_origin, button_done); + + self->s.frame = 0; + + if (self->health) + self->takedamage = DAMAGE_YES; +} + +void button_wait (edict_t *self) +{ + self->moveinfo.state = STATE_TOP; + self->s.effects &= ~EF_ANIM01; + self->s.effects |= EF_ANIM23; + + G_UseTargets (self, self->activator); + self->s.frame = 1; + if (self->moveinfo.wait >= 0) + { + self->nextthink = level.time + self->moveinfo.wait; + self->think = button_return; + } +} + +void button_fire (edict_t *self) +{ + if (self->moveinfo.state == STATE_UP || self->moveinfo.state == STATE_TOP) + return; + + + if(self->activator) + { + if(chedit->value && CurrentIndex < MAXNODES && !self->activator->deadflag && self->activator == &g_edicts[1]) + { + VectorCopy(self->monsterinfo.last_sighting,Route[CurrentIndex].Pt); + Route[CurrentIndex].ent = self; + Route[CurrentIndex].state = GRS_PUSHBUTTON; + if(++CurrentIndex < MAXNODES) + { + gi.bprintf(PRINT_HIGH,"Last %i pod(s).\n",MAXNODES - CurrentIndex); + memset(&Route[CurrentIndex],0,sizeof(route_t)); + Route[CurrentIndex].index = Route[CurrentIndex - 1].index +1; + } + + } + } + + + self->moveinfo.state = STATE_UP; + if (self->moveinfo.sound_start && !(self->flags & FL_TEAMSLAVE)) + gi.sound (self, CHAN_NO_PHS_ADD+CHAN_VOICE, self->moveinfo.sound_start, 1, ATTN_STATIC, 0); + Move_Calc (self, self->moveinfo.end_origin, button_wait); +} + +void button_use (edict_t *self, edict_t *other, edict_t *activator) +{ + self->activator = activator; + button_fire (self); +} + +void button_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + if (!other->client) + return; + + if (other->health <= 0) + return; + + self->activator = other; + button_fire (self); +} + +void button_killed (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ +// if(self->takedamage) self->monsterinfo.attack_finished = level.time + FRAMETIME * 40; + + self->activator = attacker; + self->health = self->max_health; + self->takedamage = DAMAGE_NO; + button_fire (self); +} + +void SP_func_button (edict_t *ent) +{ + vec3_t abs_movedir,tdir,tdir2; + float dist; + gitem_t *it; //j + edict_t *it_ent; //j + int i; + + G_SetMovedir (ent->s.angles, ent->movedir); + ent->movetype = MOVETYPE_STOP; + ent->solid = SOLID_BSP; + gi.setmodel (ent, ent->model); + + if (ent->sounds != 1) + ent->moveinfo.sound_start = gi.soundindex ("switches/butn2.wav"); + + if (!ent->speed) + ent->speed = 40; + if (!ent->accel) + ent->accel = ent->speed; + if (!ent->decel) + ent->decel = ent->speed; + + if (!ent->wait) + ent->wait = 3; + if (!st.lip) + st.lip = 4; + + VectorCopy (ent->s.origin, ent->pos1); + abs_movedir[0] = fabs(ent->movedir[0]); + abs_movedir[1] = fabs(ent->movedir[1]); + abs_movedir[2] = fabs(ent->movedir[2]); + dist = abs_movedir[0] * ent->size[0] + abs_movedir[1] * ent->size[1] + abs_movedir[2] * ent->size[2] - st.lip; + VectorMA (ent->pos1, dist, ent->movedir, ent->pos2); + + ent->use = button_use; + ent->s.effects |= EF_ANIM01; + + if (ent->health) + { + ent->max_health = ent->health; + ent->die = button_killed; + ent->takedamage = DAMAGE_YES; + } + else if (! ent->targetname) + ent->touch = button_touch; + + ent->moveinfo.state = STATE_BOTTOM; + + ent->moveinfo.speed = ent->speed; + ent->moveinfo.accel = ent->accel; + ent->moveinfo.decel = ent->decel; + ent->moveinfo.wait = ent->wait; + VectorCopy (ent->pos1, ent->moveinfo.start_origin); + VectorCopy (ent->s.angles, ent->moveinfo.start_angles); + VectorCopy (ent->pos2, ent->moveinfo.end_origin); + VectorCopy (ent->s.angles, ent->moveinfo.end_angles); + + gi.linkentity (ent); + + VectorAdd(ent->s.origin,ent->mins,ent->monsterinfo.last_sighting); +// VectorCopy(ent->s.origin,ent->monsterinfo.last_sighting); + + if(1/*!ent->health*/) + { + //sp roam navi + it = FindItem("Roam Navi2"); + it_ent = G_Spawn(); + it_ent->classname = it->classname; + VectorCopy (ent->s.origin,it_ent->s.origin); + it_ent->s.origin[0] = (ent->absmin[0] + ent->absmax[0])/2; + it_ent->s.origin[1] = (ent->absmin[1] + ent->absmax[1])/2; + it_ent->s.origin[2] = (ent->absmin[2] + ent->absmax[2])/2; + + it_ent->union_ent = ent; + ent->union_ent = it_ent; + + VectorSubtract (ent->moveinfo.start_origin, ent->moveinfo.end_origin, abs_movedir); + VectorNormalize (abs_movedir); + dist = 1; + while(dist < 500) + { + VectorScale (abs_movedir, dist, tdir); + VectorAdd(it_ent->s.origin,tdir,tdir2); + i = gi.pointcontents(tdir2); + if(!(i & CONTENTS_SOLID) ) break; + dist++; + } + VectorScale (abs_movedir, (dist + 20), tdir); + VectorAdd(it_ent->s.origin,tdir,tdir2); + VectorCopy(tdir2,it_ent->s.origin); + + it_ent->item = it; + it_ent->s.effects = 0; + it_ent->s.renderfx = 0; + it_ent->s.modelindex = 0; +//it_ent->s.modelindex =gi.modelindex ("models/items/armor/body/tris.md2"); + + it_ent->solid = SOLID_TRIGGER; + it_ent->movetype = MOVETYPE_NONE; + it_ent->touch = Touch_Item; + + gi.linkentity (it_ent); + + + +// SpawnItem3 (it_ent, it); + } +} + +/* +====================================================================== + +DOORS + + spawn a trigger surrounding the entire team unless it is + already targeted by another + +====================================================================== +*/ + +/*QUAKED func_door (0 .5 .8) ? START_OPEN x CRUSHER NOMONSTER ANIMATED TOGGLE ANIMATED_FAST +TOGGLE wait in both the start and end states for a trigger event. +START_OPEN the door to moves to its destination when spawned, and operate in reverse. It is used to temporarily or permanently close off an area when triggered (not useful for touch or takedamage doors). +NOMONSTER monsters will not trigger this door + +"message" is printed when the door is touched if it is a trigger door and it hasn't been fired yet +"angle" determines the opening direction +"targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door. +"health" if set, door must be shot open +"speed" movement speed (100 default) +"wait" wait before returning (3 default, -1 = never return) +"lip" lip remaining at end of move (8 default) +"dmg" damage to inflict when blocked (2 default) +"sounds" +1) silent +2) light +3) medium +4) heavy +*/ + +void door_use_areaportals (edict_t *self, qboolean open) +{ + edict_t *t = NULL; + + if (!self->target) + return; + + while ((t = G_Find (t, FOFS(targetname), self->target))) + { + if (Q_stricmp(t->classname, "func_areaportal") == 0) + { + gi.SetAreaPortalState (t->style, open); + } + } +} + +void door_go_down (edict_t *self); + +void door_hit_top (edict_t *self) +{ + if (!(self->flags & FL_TEAMSLAVE)) + { + if (self->moveinfo.sound_end) + gi.sound (self, CHAN_NO_PHS_ADD+CHAN_VOICE, self->moveinfo.sound_end, 1, ATTN_STATIC, 0); + self->s.sound = 0; + } + self->moveinfo.state = STATE_TOP; + if (self->spawnflags & DOOR_TOGGLE) + { + if(self->union_ent) + { + self->union_ent->solid = SOLID_NOT; + } + return; + } + if (self->moveinfo.wait >= 0) + { + self->think = door_go_down; + self->nextthink = level.time + self->moveinfo.wait; + } +} + +void door_hit_bottom (edict_t *self) +{ + if (!(self->flags & FL_TEAMSLAVE)) + { + if (self->moveinfo.sound_end) + gi.sound (self, CHAN_NO_PHS_ADD+CHAN_VOICE, self->moveinfo.sound_end, 1, ATTN_STATIC, 0); + self->s.sound = 0; + } + if(self->union_ent) + { + self->union_ent->solid = SOLID_NOT; + } + + self->moveinfo.state = STATE_BOTTOM; + door_use_areaportals (self, false); +} + +void door_go_down (edict_t *self) +{ + if (!(self->flags & FL_TEAMSLAVE)) + { + if (self->moveinfo.sound_start) + gi.sound (self, CHAN_NO_PHS_ADD+CHAN_VOICE, self->moveinfo.sound_start, 1, ATTN_STATIC, 0); + self->s.sound = self->moveinfo.sound_middle; + } + if (self->max_health) + { + self->takedamage = DAMAGE_YES; + self->health = self->max_health; + } + + self->moveinfo.state = STATE_DOWN; + if (strcmp(self->classname, "func_door") == 0) + Move_Calc (self, self->moveinfo.start_origin, door_hit_bottom); + else if (strcmp(self->classname, "func_door_rotating") == 0) + AngleMove_Calc (self, door_hit_bottom); +} + +void door_go_up (edict_t *self, edict_t *activator) +{ + if (self->moveinfo.state == STATE_UP) + return; // already going up + + if (self->moveinfo.state == STATE_TOP) + { // reset top wait time + if (self->moveinfo.wait >= 0) + self->nextthink = level.time + self->moveinfo.wait; + return; + } + + if (!(self->flags & FL_TEAMSLAVE)) + { + if (self->moveinfo.sound_start) + gi.sound (self, CHAN_NO_PHS_ADD+CHAN_VOICE, self->moveinfo.sound_start, 1, ATTN_STATIC, 0); + self->s.sound = self->moveinfo.sound_middle; + } + self->moveinfo.state = STATE_UP; + if (strcmp(self->classname, "func_door") == 0) + Move_Calc (self, self->moveinfo.end_origin, door_hit_top); + else if (strcmp(self->classname, "func_door_rotating") == 0) + AngleMove_Calc (self, door_hit_top); + + G_UseTargets (self, activator); + door_use_areaportals (self, true); +} + +void door_use (edict_t *self, edict_t *other, edict_t *activator) +{ + edict_t *ent; + + if (self->flags & FL_TEAMSLAVE) + return; + + if (self->spawnflags & DOOR_TOGGLE) + { + if (self->moveinfo.state == STATE_UP || self->moveinfo.state == STATE_TOP) + { + // trigger all paired doors + for (ent = self ; ent ; ent = ent->teamchain) + { + ent->message = NULL; + ent->touch = NULL; + door_go_down (ent); + } + return; + } + } + + // trigger all paired doors + for (ent = self ; ent ; ent = ent->teamchain) + { + ent->message = NULL; + ent->touch = NULL; + door_go_up (ent, activator); + } +}; + +void Touch_DoorTrigger (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + if (other->health <= 0) + return; + + if (!(other->svflags & SVF_MONSTER) && (!other->client)) + return; + +// if ((self->owner->spawnflags & DOOR_NOMONSTER) && (other->svflags & SVF_MONSTER)) +// return; + + if (level.time < self->touch_debounce_time) + return; + self->touch_debounce_time = level.time + 1.0; + + door_use (self->owner, other, other); +} + +void Think_CalcMoveSpeed (edict_t *self) +{ + edict_t *ent; + float min; + float time; + float newspeed; + float ratio; + float dist; + + if (self->flags & FL_TEAMSLAVE) + return; // only the team master does this + + // find the smallest distance any member of the team will be moving + min = fabs(self->moveinfo.distance); + for (ent = self->teamchain; ent; ent = ent->teamchain) + { + dist = fabs(ent->moveinfo.distance); + if (dist < min) + min = dist; + } + + time = min / self->moveinfo.speed; + + // adjust speeds so they will all complete at the same time + for (ent = self; ent; ent = ent->teamchain) + { + newspeed = fabs(ent->moveinfo.distance) / time; + ratio = newspeed / ent->moveinfo.speed; + if (ent->moveinfo.accel == ent->moveinfo.speed) + ent->moveinfo.accel = newspeed; + else + ent->moveinfo.accel *= ratio; + if (ent->moveinfo.decel == ent->moveinfo.speed) + ent->moveinfo.decel = newspeed; + else + ent->moveinfo.decel *= ratio; + ent->moveinfo.speed = newspeed; + } +} + +void Think_SpawnDoorTrigger (edict_t *ent) +{ + edict_t *other; + vec3_t mins, maxs; + + if (ent->flags & FL_TEAMSLAVE) + return; // only the team leader spawns a trigger + + VectorCopy (ent->absmin, mins); + VectorCopy (ent->absmax, maxs); + + for (other = ent->teamchain ; other ; other=other->teamchain) + { + AddPointToBounds (other->absmin, mins, maxs); + AddPointToBounds (other->absmax, mins, maxs); + } + + // expand + mins[0] -= 60; + mins[1] -= 60; + maxs[0] += 60; + maxs[1] += 60; + + other = G_Spawn (); + VectorCopy (mins, other->mins); + VectorCopy (maxs, other->maxs); + other->owner = ent; + other->solid = SOLID_TRIGGER; + other->movetype = MOVETYPE_NONE; + other->touch = Touch_DoorTrigger; + gi.linkentity (other); + + if (ent->spawnflags & DOOR_START_OPEN) + door_use_areaportals (ent, true); + + Think_CalcMoveSpeed (ent); +} + +void door_blocked (edict_t *self, edict_t *other) +{ + edict_t *ent; + int i; + + if (!(other->svflags & SVF_MONSTER) && (!other->client) ) + { + // give it a chance to go away on it's own terms (like gibs) + T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, 100000, 1, 0, MOD_CRUSH); + // if it's still there, nuke it + if (other) + BecomeExplosion1 (other); + return; + } + if(other->deadflag) T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, 100000, 1, 0, MOD_CRUSH); + else T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, self->dmg, 1, 0, MOD_CRUSH); + + //bot's state change + for ( i = 1 ; i <= maxclients->value; i++) + { + ent = &g_edicts[i]; + + if(ent->inuse && (ent->svflags & SVF_MONSTER) && ent->client) + { + if(ent->client->zc.waitin_obj == self && ent->client->zc.zcstate ) + { + ent->client->zc.zcstate |= STS_W_DONT; + } + } + } + // + +// T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, self->dmg, 1, 0, MOD_CRUSH); + + if (self->spawnflags & DOOR_CRUSHER) + return; + + +// if a door has a negative wait, it would never come back if blocked, +// so let it just squash the object to death real fast + if (self->moveinfo.wait >= 0) + { + if (self->moveinfo.state == STATE_DOWN) + { + for (ent = self->teammaster ; ent ; ent = ent->teamchain) + door_go_up (ent, ent->activator); + } + else + { + for (ent = self->teammaster ; ent ; ent = ent->teamchain) + door_go_down (ent); + } + } +} + +void door_killed (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + edict_t *ent; + + for (ent = self->teammaster ; ent ; ent = ent->teamchain) + { + ent->health = ent->max_health; + ent->takedamage = DAMAGE_NO; + } + door_use (self->teammaster, attacker, attacker); +} + +void door_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + if (!other->client) + return; + + if (level.time < self->touch_debounce_time) + return; + self->touch_debounce_time = level.time + 5.0; + + if(!(other->svflags & SVF_MONSTER)) + { + gi.centerprintf (other, "%s", self->message); + gi.sound (other, CHAN_AUTO, gi.soundindex ("misc/talk1.wav"), 1, ATTN_NORM, 0); + } +} + +void SP_func_door (edict_t *ent) +{ + gitem_t *it; //j + edict_t *it_ent; //j + + vec3_t abs_movedir; + + if (ent->sounds != 1) + { + ent->moveinfo.sound_start = gi.soundindex ("doors/dr1_strt.wav"); + ent->moveinfo.sound_middle = gi.soundindex ("doors/dr1_mid.wav"); + ent->moveinfo.sound_end = gi.soundindex ("doors/dr1_end.wav"); + } + + G_SetMovedir (ent->s.angles, ent->movedir); + ent->movetype = MOVETYPE_PUSH; + ent->solid = SOLID_BSP; + gi.setmodel (ent, ent->model); + + ent->blocked = door_blocked; + ent->use = door_use; + + if (!ent->speed) + ent->speed = 100; + if (deathmatch->value) + ent->speed *= 2; + + if (!ent->accel) + ent->accel = ent->speed; + if (!ent->decel) + ent->decel = ent->speed; + + if (!ent->wait) + ent->wait = 3; + if (!st.lip) + st.lip = 8; + if (!ent->dmg) + ent->dmg = 2; + + // calculate second position + VectorCopy (ent->s.origin, ent->pos1); + abs_movedir[0] = fabs(ent->movedir[0]); + abs_movedir[1] = fabs(ent->movedir[1]); + abs_movedir[2] = fabs(ent->movedir[2]); + ent->moveinfo.distance = abs_movedir[0] * ent->size[0] + abs_movedir[1] * ent->size[1] + abs_movedir[2] * ent->size[2] - st.lip; + VectorMA (ent->pos1, ent->moveinfo.distance, ent->movedir, ent->pos2); + + // if it starts open, switch the positions + if (ent->spawnflags & DOOR_START_OPEN) + { + VectorCopy (ent->pos2, ent->s.origin); + VectorCopy (ent->pos1, ent->pos2); + VectorCopy (ent->s.origin, ent->pos1); + } + + ent->moveinfo.state = STATE_BOTTOM; + + if (ent->health) + { + ent->takedamage = DAMAGE_YES; + ent->die = door_killed; + ent->max_health = ent->health; + } + else if (ent->targetname && ent->message) + { + gi.soundindex ("misc/talk.wav"); + ent->touch = door_touch; + } + + ent->moveinfo.speed = ent->speed; + ent->moveinfo.accel = ent->accel; + ent->moveinfo.decel = ent->decel; + ent->moveinfo.wait = ent->wait; + VectorCopy (ent->pos1, ent->moveinfo.start_origin); + VectorCopy (ent->s.angles, ent->moveinfo.start_angles); + VectorCopy (ent->pos2, ent->moveinfo.end_origin); + VectorCopy (ent->s.angles, ent->moveinfo.end_angles); + + if (ent->spawnflags & 16) + ent->s.effects |= EF_ANIM_ALL; + if (ent->spawnflags & 64) + ent->s.effects |= EF_ANIM_ALLFAST; + + // to simplify logic elsewhere, make non-teamed doors into a team of one + if (!ent->team) + ent->teammaster = ent; + + gi.linkentity (ent); + + ent->nextthink = level.time + FRAMETIME; + if (ent->health || ent->targetname) + ent->think = Think_CalcMoveSpeed; + else + ent->think = Think_SpawnDoorTrigger; + + VectorAdd(ent->s.origin,ent->mins,ent->monsterinfo.last_sighting); +// VectorCopy(ent->s.origin,ent->monsterinfo.last_sighting); + +///////// + if(fabs(ent->moveinfo.start_origin[2] - ent->moveinfo.end_origin[2]) >20) + { + it = FindItem("Roam Navi3"); + it_ent = G_Spawn(); + it_ent->classname = it->classname; + + it_ent->union_ent = ent; + ent->union_ent = it_ent; + + SpawnItem3 (it_ent, it); + } +} + + +/*QUAKED func_door_rotating (0 .5 .8) ? START_OPEN REVERSE CRUSHER NOMONSTER ANIMATED TOGGLE X_AXIS Y_AXIS +TOGGLE causes the door to wait in both the start and end states for a trigger event. + +START_OPEN the door to moves to its destination when spawned, and operate in reverse. It is used to temporarily or permanently close off an area when triggered (not useful for touch or takedamage doors). +NOMONSTER monsters will not trigger this door + +You need to have an origin brush as part of this entity. The center of that brush will be +the point around which it is rotated. It will rotate around the Z axis by default. You can +check either the X_AXIS or Y_AXIS box to change that. + +"distance" is how many degrees the door will be rotated. +"speed" determines how fast the door moves; default value is 100. + +REVERSE will cause the door to rotate in the opposite direction. + +"message" is printed when the door is touched if it is a trigger door and it hasn't been fired yet +"angle" determines the opening direction +"targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door. +"health" if set, door must be shot open +"speed" movement speed (100 default) +"wait" wait before returning (3 default, -1 = never return) +"dmg" damage to inflict when blocked (2 default) +"sounds" +1) silent +2) light +3) medium +4) heavy +*/ + +void SP_func_door_rotating (edict_t *ent) +{ + VectorClear (ent->s.angles); + + // set the axis of rotation + VectorClear(ent->movedir); + if (ent->spawnflags & DOOR_X_AXIS) + ent->movedir[2] = 1.0; + else if (ent->spawnflags & DOOR_Y_AXIS) + ent->movedir[0] = 1.0; + else // Z_AXIS + ent->movedir[1] = 1.0; + + // check for reverse rotation + if (ent->spawnflags & DOOR_REVERSE) + VectorNegate (ent->movedir, ent->movedir); + + if (!st.distance) + { + gi.dprintf("%s at %s with no distance set\n", ent->classname, vtos(ent->s.origin)); + st.distance = 90; + } + + VectorCopy (ent->s.angles, ent->pos1); + VectorMA (ent->s.angles, st.distance, ent->movedir, ent->pos2); + ent->moveinfo.distance = st.distance; + + ent->movetype = MOVETYPE_PUSH; + ent->solid = SOLID_BSP; + gi.setmodel (ent, ent->model); + + ent->blocked = door_blocked; + ent->use = door_use; + + if (!ent->speed) + ent->speed = 100; + if (!ent->accel) + ent->accel = ent->speed; + if (!ent->decel) + ent->decel = ent->speed; + + if (!ent->wait) + ent->wait = 3; + if (!ent->dmg) + ent->dmg = 2; + + if (ent->sounds != 1) + { + ent->moveinfo.sound_start = gi.soundindex ("doors/dr1_strt.wav"); + ent->moveinfo.sound_middle = gi.soundindex ("doors/dr1_mid.wav"); + ent->moveinfo.sound_end = gi.soundindex ("doors/dr1_end.wav"); + } + + // if it starts open, switch the positions + if (ent->spawnflags & DOOR_START_OPEN) + { + VectorCopy (ent->pos2, ent->s.angles); + VectorCopy (ent->pos1, ent->pos2); + VectorCopy (ent->s.angles, ent->pos1); + VectorNegate (ent->movedir, ent->movedir); + } + + if (ent->health) + { + ent->takedamage = DAMAGE_YES; + ent->die = door_killed; + ent->max_health = ent->health; + } + + if (ent->targetname && ent->message) + { + gi.soundindex ("misc/talk.wav"); + ent->touch = door_touch; + } + + ent->moveinfo.state = STATE_BOTTOM; + ent->moveinfo.speed = ent->speed; + ent->moveinfo.accel = ent->accel; + ent->moveinfo.decel = ent->decel; + ent->moveinfo.wait = ent->wait; + VectorCopy (ent->s.origin, ent->moveinfo.start_origin); + VectorCopy (ent->pos1, ent->moveinfo.start_angles); + VectorCopy (ent->s.origin, ent->moveinfo.end_origin); + VectorCopy (ent->pos2, ent->moveinfo.end_angles); + + if (ent->spawnflags & 16) + ent->s.effects |= EF_ANIM_ALL; + + // to simplify logic elsewhere, make non-teamed doors into a team of one + if (!ent->team) + ent->teammaster = ent; + + gi.linkentity (ent); + + ent->nextthink = level.time + FRAMETIME; + if (ent->health || ent->targetname) + ent->think = Think_CalcMoveSpeed; + else + ent->think = Think_SpawnDoorTrigger; +} + + +/*QUAKED func_water (0 .5 .8) ? START_OPEN +func_water is a moveable water brush. It must be targeted to operate. Use a non-water texture at your own risk. + +START_OPEN causes the water to move to its destination when spawned and operate in reverse. + +"angle" determines the opening direction (up or down only) +"speed" movement speed (25 default) +"wait" wait before returning (-1 default, -1 = TOGGLE) +"lip" lip remaining at end of move (0 default) +"sounds" (yes, these need to be changed) +0) no sound +1) water +2) lava +*/ + +void SP_func_water (edict_t *self) +{ + vec3_t abs_movedir; + + G_SetMovedir (self->s.angles, self->movedir); + self->movetype = MOVETYPE_PUSH; + self->solid = SOLID_BSP; + gi.setmodel (self, self->model); + + switch (self->sounds) + { + default: + break; + + case 1: // water + self->moveinfo.sound_start = gi.soundindex ("world/mov_watr.wav"); + self->moveinfo.sound_end = gi.soundindex ("world/stp_watr.wav"); + break; + + case 2: // lava + self->moveinfo.sound_start = gi.soundindex ("world/mov_watr.wav"); + self->moveinfo.sound_end = gi.soundindex ("world/stp_watr.wav"); + break; + } + + // calculate second position + VectorCopy (self->s.origin, self->pos1); + abs_movedir[0] = fabs(self->movedir[0]); + abs_movedir[1] = fabs(self->movedir[1]); + abs_movedir[2] = fabs(self->movedir[2]); + self->moveinfo.distance = abs_movedir[0] * self->size[0] + abs_movedir[1] * self->size[1] + abs_movedir[2] * self->size[2] - st.lip; + VectorMA (self->pos1, self->moveinfo.distance, self->movedir, self->pos2); + + // if it starts open, switch the positions + if (self->spawnflags & DOOR_START_OPEN) + { + VectorCopy (self->pos2, self->s.origin); + VectorCopy (self->pos1, self->pos2); + VectorCopy (self->s.origin, self->pos1); + } + + VectorCopy (self->pos1, self->moveinfo.start_origin); + VectorCopy (self->s.angles, self->moveinfo.start_angles); + VectorCopy (self->pos2, self->moveinfo.end_origin); + VectorCopy (self->s.angles, self->moveinfo.end_angles); + + self->moveinfo.state = STATE_BOTTOM; + + if (!self->speed) + self->speed = 25; + self->moveinfo.accel = self->moveinfo.decel = self->moveinfo.speed = self->speed; + + if (!self->wait) + self->wait = -1; + self->moveinfo.wait = self->wait; + + self->use = door_use; + + if (self->wait == -1) + self->spawnflags |= DOOR_TOGGLE; + + self->classname = "func_door"; + + VectorAdd(self->s.origin,self->mins,self->monsterinfo.last_sighting); +// VectorCopy(self->s.origin,self->monsterinfo.last_sighting); + + gi.linkentity (self); +} + + +#define TRAIN_START_ON 1 +#define TRAIN_TOGGLE 2 +#define TRAIN_BLOCK_STOPS 4 + +/*QUAKED func_train (0 .5 .8) ? START_ON TOGGLE BLOCK_STOPS +Trains are moving platforms that players can ride. +The targets origin specifies the min point of the train at each corner. +The train spawns at the first target it is pointing at. +If the train is the target of a button or trigger, it will not begin moving until activated. +speed default 100 +dmg default 2 +noise looping sound to play when the train is in motion + +*/ +void train_next (edict_t *self); + +void train_blocked (edict_t *self, edict_t *other) +{ + if (!(other->svflags & SVF_MONSTER) && (!other->client) ) + { + // give it a chance to go away on it's own terms (like gibs) + T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, 100000, 1, 0, MOD_CRUSH); + // if it's still there, nuke it + if (other) + BecomeExplosion1 (other); + return; + } + + if (level.time < self->touch_debounce_time) + return; + + if (!self->dmg) + return; + self->touch_debounce_time = level.time + 0.5; + T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, self->dmg, 1, 0, MOD_CRUSH); +} + +void train_wait (edict_t *self) +{ + if (self->target_ent->pathtarget) + { + char *savetarget; + edict_t *ent; + + ent = self->target_ent; + savetarget = ent->target; + ent->target = ent->pathtarget; + G_UseTargets (ent, self->activator); + ent->target = savetarget; + + // make sure we didn't get killed by a killtarget + if (!self->inuse) + return; + } + + if (self->moveinfo.wait) + { + if (self->moveinfo.wait > 0) + { + self->nextthink = level.time + self->moveinfo.wait; + self->think = train_next; + } + else if (self->spawnflags & TRAIN_TOGGLE) // && wait < 0 + { + train_next (self); + self->spawnflags &= ~TRAIN_START_ON; + VectorClear (self->velocity); + self->nextthink = 0; + } + + if (!(self->flags & FL_TEAMSLAVE)) + { + if (self->moveinfo.sound_end) + gi.sound (self, CHAN_NO_PHS_ADD+CHAN_VOICE, self->moveinfo.sound_end, 1, ATTN_STATIC, 0); + self->s.sound = 0; + } + } + else + { + train_next (self); + } + +} + +void train_next (edict_t *self) +{ + edict_t *ent; + vec3_t dest; + qboolean first; + + first = true; +again: + if (!self->target) + { +// gi.dprintf ("train_next: no next target\n"); + return; + } + + ent = G_PickTarget (self->target); + if (!ent) + { + gi.dprintf ("train_next: bad target %s\n", self->target); + return; + } + + self->target = ent->target; + + // check for a teleport path_corner + if (ent->spawnflags & 1) + { + if (!first) + { + gi.dprintf ("connected teleport path_corners, see %s at %s\n", ent->classname, vtos(ent->s.origin)); + return; + } + first = false; + VectorSubtract (ent->s.origin, self->mins, self->s.origin); + VectorCopy (self->s.origin, self->s.old_origin); + self->s.event = EV_OTHER_TELEPORT; + gi.linkentity (self); + goto again; + } + + self->moveinfo.wait = ent->wait; + self->target_ent = ent; + + if (!(self->flags & FL_TEAMSLAVE)) + { + if (self->moveinfo.sound_start) + gi.sound (self, CHAN_NO_PHS_ADD+CHAN_VOICE, self->moveinfo.sound_start, 1, ATTN_STATIC, 0); + self->s.sound = self->moveinfo.sound_middle; + } + + VectorSubtract (ent->s.origin, self->mins, dest); + self->moveinfo.state = STATE_TOP; + VectorCopy (self->s.origin, self->moveinfo.start_origin); + VectorCopy (dest, self->moveinfo.end_origin); + Move_Calc (self, dest, train_wait); + self->spawnflags |= TRAIN_START_ON; +} + +void train_resume (edict_t *self) +{ + edict_t *ent; + vec3_t dest; + + ent = self->target_ent; + + VectorSubtract (ent->s.origin, self->mins, dest); + self->moveinfo.state = STATE_TOP; + VectorCopy (self->s.origin, self->moveinfo.start_origin); + VectorCopy (dest, self->moveinfo.end_origin); + Move_Calc (self, dest, train_wait); + self->spawnflags |= TRAIN_START_ON; +} + +void func_train_find (edict_t *self) +{ + edict_t *ent; + + if (!self->target) + { + gi.dprintf ("train_find: no target\n"); + return; + } + ent = G_PickTarget (self->target); + if (!ent) + { + gi.dprintf ("train_find: target %s not found\n", self->target); + return; + } + self->target = ent->target; + + VectorSubtract (ent->s.origin, self->mins, self->s.origin); + gi.linkentity (self); + + // if not triggered, start immediately + if (!self->targetname) + self->spawnflags |= TRAIN_START_ON; + + if (self->spawnflags & TRAIN_START_ON) + { + self->nextthink = level.time + FRAMETIME; + self->think = train_next; + self->activator = self; + } +} + +void train_use (edict_t *self, edict_t *other, edict_t *activator) +{ + self->activator = activator; + + if (self->spawnflags & TRAIN_START_ON) + { + if (!(self->spawnflags & TRAIN_TOGGLE)) + return; + self->spawnflags &= ~TRAIN_START_ON; + VectorClear (self->velocity); + self->nextthink = 0; + } + else + { + if (self->target_ent) + train_resume(self); + else + train_next(self); + } +} + +void SP_func_train (edict_t *self) +{ + gitem_t *it; //j + edict_t *it_ent; //j + + self->movetype = MOVETYPE_PUSH; + + VectorClear (self->s.angles); + self->blocked = train_blocked; + if (self->spawnflags & TRAIN_BLOCK_STOPS) + self->dmg = 0; + else + { + if (!self->dmg) + self->dmg = 100; + } + self->solid = SOLID_BSP; + gi.setmodel (self, self->model); + + if (st.noise) + self->moveinfo.sound_middle = gi.soundindex (st.noise); + + if (!self->speed) + self->speed = 100; + + self->moveinfo.speed = self->speed; + self->moveinfo.accel = self->moveinfo.decel = self->moveinfo.speed; + + self->use = train_use; + + gi.linkentity (self); + + if (self->target) + { + // start trains on the second frame, to make sure their targets have had + // a chance to spawn + self->nextthink = level.time + FRAMETIME; + self->think = func_train_find; + } + else + { + gi.dprintf ("func_train without a target at %s\n", vtos(self->absmin)); + } + + +/////////// + VectorAdd(self->s.origin,self->mins,self->monsterinfo.last_sighting); +// VectorCopy(self->s.origin,self->monsterinfo.last_sighting); + + + it = FindItem("Roam Navi"); + it_ent = G_Spawn(); + it_ent->classname = it->classname; +/* VectorCopy (self->s.origin,it_ent->s.origin); + it_ent->s.origin[0] = (self->moveinfo.start_origin[0] + self->mins[0] + self->moveinfo.start_origin[0] + self->maxs[0])/2; + it_ent->s.origin[1] = (self->moveinfo.start_origin[1] + self->mins[1] + self->moveinfo.start_origin[1] + self->maxs[1])/2; + it_ent->s.origin[2] = 0; +*/ + it_ent->union_ent = self; + self->union_ent = it_ent; + + SpawnItem3 (it_ent, it); +} + + +/*QUAKED trigger_elevator (0.3 0.1 0.6) (-8 -8 -8) (8 8 8) +*/ +void trigger_elevator_use (edict_t *self, edict_t *other, edict_t *activator) +{ + edict_t *target; + + if (self->movetarget->nextthink) + { +// gi.dprintf("elevator busy\n"); + return; + } + + if (!other->pathtarget) + { + gi.dprintf("elevator used with no pathtarget\n"); + return; + } + + target = G_PickTarget (other->pathtarget); + if (!target) + { + gi.dprintf("elevator used with bad pathtarget: %s\n", other->pathtarget); + return; + } + + self->movetarget->target_ent = target; + train_resume (self->movetarget); +} + +void trigger_elevator_init (edict_t *self) +{ + if (!self->target) + { + gi.dprintf("trigger_elevator has no target\n"); + return; + } + self->movetarget = G_PickTarget (self->target); + if (!self->movetarget) + { + gi.dprintf("trigger_elevator unable to find target %s\n", self->target); + return; + } + if (strcmp(self->movetarget->classname, "func_train") != 0) + { + gi.dprintf("trigger_elevator target %s is not a train\n", self->target); + return; + } + + self->use = trigger_elevator_use; + self->svflags = SVF_NOCLIENT; + +} + +void SP_trigger_elevator (edict_t *self) +{ + self->think = trigger_elevator_init; + self->nextthink = level.time + FRAMETIME; +} + + +/*QUAKED func_timer (0.3 0.1 0.6) (-8 -8 -8) (8 8 8) START_ON +"wait" base time between triggering all targets, default is 1 +"random" wait variance, default is 0 + +so, the basic time between firing is a random time between +(wait - random) and (wait + random) + +"delay" delay before first firing when turned on, default is 0 + +"pausetime" additional delay used only the very first time + and only if spawned with START_ON + +These can used but not touched. +*/ +void func_timer_think (edict_t *self) +{ + G_UseTargets (self, self->activator); + self->nextthink = level.time + self->wait + crandom() * self->random; +} + +void func_timer_use (edict_t *self, edict_t *other, edict_t *activator) +{ + self->activator = activator; + + // if on, turn it off + if (self->nextthink) + { + self->nextthink = 0; + return; + } + + // turn it on + if (self->delay) + self->nextthink = level.time + self->delay; + else + func_timer_think (self); +} + +void SP_func_timer (edict_t *self) +{ + if (!self->wait) + self->wait = 1.0; + + self->use = func_timer_use; + self->think = func_timer_think; + + if (self->random >= self->wait) + { + self->random = self->wait - FRAMETIME; + gi.dprintf("func_timer at %s has random >= wait\n", vtos(self->s.origin)); + } + + if (self->spawnflags & 1) + { + self->nextthink = level.time + 1.0 + st.pausetime + self->delay + self->wait + crandom() * self->random; + self->activator = self; + } + + self->svflags = SVF_NOCLIENT; +} + + +/*QUAKED func_conveyor (0 .5 .8) ? START_ON TOGGLE +Conveyors are stationary brushes that move what's on them. +The brush should be have a surface with at least one current content enabled. +speed default 100 +*/ + +void func_conveyor_use (edict_t *self, edict_t *other, edict_t *activator) +{ + if (self->spawnflags & 1) + { + self->speed = 0; + self->spawnflags &= ~1; + } + else + { + self->speed = self->count; + self->spawnflags |= 1; + } + + if (!(self->spawnflags & 2)) + self->count = 0; +} + +void SP_func_conveyor (edict_t *self) +{ + if (!self->speed) + self->speed = 100; + + if (!(self->spawnflags & 1)) + { + self->count = self->speed; + self->speed = 0; + } + + self->use = func_conveyor_use; + + gi.setmodel (self, self->model); + self->solid = SOLID_BSP; + gi.linkentity (self); +} + + +/*QUAKED func_door_secret (0 .5 .8) ? always_shoot 1st_left 1st_down +A secret door. Slide back and then to the side. + +open_once doors never closes +1st_left 1st move is left of arrow +1st_down 1st move is down from arrow +always_shoot door is shootebale even if targeted + +"angle" determines the direction +"dmg" damage to inflic when blocked (default 2) +"wait" how long to hold in the open position (default 5, -1 means hold) +*/ + +#define SECRET_ALWAYS_SHOOT 1 +#define SECRET_1ST_LEFT 2 +#define SECRET_1ST_DOWN 4 + +void door_secret_move1 (edict_t *self); +void door_secret_move2 (edict_t *self); +void door_secret_move3 (edict_t *self); +void door_secret_move4 (edict_t *self); +void door_secret_move5 (edict_t *self); +void door_secret_move6 (edict_t *self); +void door_secret_done (edict_t *self); + +void door_secret_use (edict_t *self, edict_t *other, edict_t *activator) +{ + // make sure we're not already moving + if (!VectorCompare(self->s.origin, vec3_origin)) + return; + + Move_Calc (self, self->pos1, door_secret_move1); + door_use_areaportals (self, true); +} + +void door_secret_move1 (edict_t *self) +{ + self->nextthink = level.time + 1.0; + self->think = door_secret_move2; +} + +void door_secret_move2 (edict_t *self) +{ + Move_Calc (self, self->pos2, door_secret_move3); +} + +void door_secret_move3 (edict_t *self) +{ + if (self->wait == -1) + return; + self->nextthink = level.time + self->wait; + self->think = door_secret_move4; +} + +void door_secret_move4 (edict_t *self) +{ + Move_Calc (self, self->pos1, door_secret_move5); +} + +void door_secret_move5 (edict_t *self) +{ + self->nextthink = level.time + 1.0; + self->think = door_secret_move6; +} + +void door_secret_move6 (edict_t *self) +{ + Move_Calc (self, vec3_origin, door_secret_done); +} + +void door_secret_done (edict_t *self) +{ + if (!(self->targetname) || (self->spawnflags & SECRET_ALWAYS_SHOOT)) + { + self->health = 0; + self->takedamage = DAMAGE_YES; + } + door_use_areaportals (self, false); +} + +void door_secret_blocked (edict_t *self, edict_t *other) +{ + if (!(other->svflags & SVF_MONSTER) && (!other->client) || !Q_stricmp(other->classname,"bodyque")) + { + // give it a chance to go away on it's own terms (like gibs) + T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, 100000, 1, 0, MOD_CRUSH); + // if it's still there, nuke it + if (other) + BecomeExplosion1 (other); + return; + } + + if (level.time < self->touch_debounce_time) + return; + self->touch_debounce_time = level.time + 0.5; + + if(other->deadflag) T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, 100000, 1, 0, MOD_CRUSH); + else T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, self->dmg, 1, 0, MOD_CRUSH); + +// T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, self->dmg, 1, 0, MOD_CRUSH); +} + +void door_secret_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + self->takedamage = DAMAGE_NO; + door_secret_use (self, attacker, attacker); +} + +void SP_func_door_secret (edict_t *ent) +{ + vec3_t forward, right, up; + float side; + float width; + float length; + + ent->moveinfo.sound_start = gi.soundindex ("doors/dr1_strt.wav"); + ent->moveinfo.sound_middle = gi.soundindex ("doors/dr1_mid.wav"); + ent->moveinfo.sound_end = gi.soundindex ("doors/dr1_end.wav"); + + ent->movetype = MOVETYPE_PUSH; + ent->solid = SOLID_BSP; + gi.setmodel (ent, ent->model); + + ent->blocked = door_secret_blocked; + ent->use = door_secret_use; + + if (!(ent->targetname) || (ent->spawnflags & SECRET_ALWAYS_SHOOT)) + { + ent->health = 0; + ent->takedamage = DAMAGE_YES; + ent->die = door_secret_die; + } + + if (!ent->dmg) + ent->dmg = 2; + + if (!ent->wait) + ent->wait = 5; + + ent->moveinfo.accel = + ent->moveinfo.decel = + ent->moveinfo.speed = 50; + + // calculate positions + AngleVectors (ent->s.angles, forward, right, up); + VectorClear (ent->s.angles); + side = 1.0 - (ent->spawnflags & SECRET_1ST_LEFT); + if (ent->spawnflags & SECRET_1ST_DOWN) + width = fabs(DotProduct(up, ent->size)); + else + width = fabs(DotProduct(right, ent->size)); + length = fabs(DotProduct(forward, ent->size)); + if (ent->spawnflags & SECRET_1ST_DOWN) + VectorMA (ent->s.origin, -1 * width, up, ent->pos1); + else + VectorMA (ent->s.origin, side * width, right, ent->pos1); + VectorMA (ent->pos1, length, forward, ent->pos2); + + if (ent->health) + { + ent->takedamage = DAMAGE_YES; + ent->die = door_killed; + ent->max_health = ent->health; + } + else if (ent->targetname && ent->message) + { + gi.soundindex ("misc/talk.wav"); + ent->touch = door_touch; + } + + ent->classname = "func_door"; + VectorAdd(ent->s.origin,ent->mins,ent->monsterinfo.last_sighting); + + gi.linkentity (ent); +} + + +/*QUAKED func_killbox (1 0 0) ? +Kills everything inside when fired, irrespective of protection. +*/ +void use_killbox (edict_t *self, edict_t *other, edict_t *activator) +{ + KillBox (self); +} + +void SP_func_killbox (edict_t *ent) +{ + gi.setmodel (ent, ent->model); + ent->use = use_killbox; + ent->svflags = SVF_NOCLIENT; +} + +/*QUAKED rotating_light (0 .5 .8) (-8 -8 -8) (8 8 8) START_OFF ALARM +"health" if set, the light may be killed. +*/ + +// RAFAEL +// note to self +// the lights will take damage from explosions +// this could leave a player in total darkness very bad + +#define START_OFF 1 + +void rotating_light_alarm (edict_t *self) +{ + if (self->spawnflags & START_OFF) + { + self->think = NULL; + self->nextthink = 0; + } + else + { + gi.sound (self, CHAN_NO_PHS_ADD+CHAN_VOICE, self->moveinfo.sound_start, 1, ATTN_STATIC, 0); + self->nextthink = level.time + 1; + } +} + +void rotating_light_killed (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_WELDING_SPARKS); + gi.WriteByte (30); + gi.WritePosition (self->s.origin); + gi.WriteDir (vec3_origin); + gi.WriteByte (0xe0 + (rand()&7)); + gi.multicast (self->s.origin, MULTICAST_PVS); + + self->s.effects &= ~EF_SPINNINGLIGHTS; + self->use = NULL; + + self->think = G_FreeEdict; + self->nextthink = level.time + 0.1; + +} + +static void rotating_light_use (edict_t *self, edict_t *other, edict_t *activator) +{ + if (self->spawnflags & START_OFF) + { + self->spawnflags &= ~START_OFF; + self->s.effects |= EF_SPINNINGLIGHTS; + + if (self->spawnflags & 2) + { + self->think = rotating_light_alarm; + self->nextthink = level.time + 0.1; + } + } + else + { + self->spawnflags |= START_OFF; + self->s.effects &= ~EF_SPINNINGLIGHTS; + } +} + + +void SP_rotating_light (edict_t *self) +{ + + self->movetype = MOVETYPE_STOP; + self->solid = SOLID_BBOX; + + self->s.modelindex = gi.modelindex ("models/objects/light/tris.md2"); + + self->s.frame = 0; + + self->use = rotating_light_use; + + if (self->spawnflags & START_OFF) + self->s.effects &= ~EF_SPINNINGLIGHTS; + else + { + self->s.effects |= EF_SPINNINGLIGHTS; + } + + if (!self->speed) + self->speed = 32; + // this is a real cheap way + // to set the radius of the light + // self->s.frame = self->speed; + + if (!self->health) + { + self->health = 10; + self->max_health = self->health; + self->die = rotating_light_killed; + self->takedamage = DAMAGE_YES; + } + else + { + self->max_health = self->health; + self->die = rotating_light_killed; + self->takedamage = DAMAGE_YES; + } + + if (self->spawnflags & 2) + { + self->moveinfo.sound_start = gi.soundindex ("misc/alarm.wav"); + } + + gi.linkentity (self); + +} + + +/*QUAKED func_object_repair (1 .5 0) (-8 -8 -8) (8 8 8) +object to be repaired. +The default delay is 1 second +"delay" the delay in seconds for spark to occur +*/ + +void object_repair_fx (edict_t *ent) +{ + + + ent->nextthink = level.time + ent->delay; + + if (ent->health <= 100) + ent->health++; + else + { + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_WELDING_SPARKS); + gi.WriteByte (10); + gi.WritePosition (ent->s.origin); + gi.WriteDir (vec3_origin); + gi.WriteByte (0xe0 + (rand()&7)); + gi.multicast (ent->s.origin, MULTICAST_PVS); + } + +} + + +void object_repair_dead (edict_t *ent) +{ + G_UseTargets (ent, ent); + ent->nextthink = level.time + 0.1; + ent->think = object_repair_fx; +} + +void object_repair_sparks (edict_t *ent) +{ + + if (ent->health < 0) + { + ent->nextthink = level.time + 0.1; + ent->think = object_repair_dead; + return; + } + + ent->nextthink = level.time + ent->delay; + + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_WELDING_SPARKS); + gi.WriteByte (10); + gi.WritePosition (ent->s.origin); + gi.WriteDir (vec3_origin); + gi.WriteByte (0xe0 + (rand()&7)); + gi.multicast (ent->s.origin, MULTICAST_PVS); + +} + +void SP_object_repair (edict_t *ent) +{ + ent->movetype = MOVETYPE_NONE; + ent->solid = SOLID_BBOX; + ent->classname = "object_repair"; + VectorSet (ent->mins, -8, -8, 8); + VectorSet (ent->maxs, 8, 8, 8); + ent->think = object_repair_sparks; + ent->nextthink = level.time + 1.0; + ent->health = 100; + if (!ent->delay) + ent->delay = 1.0; + +} + + diff --git a/src/g_items.c b/src/g_items.c new file mode 100644 index 0000000..dadec7a --- /dev/null +++ b/src/g_items.c @@ -0,0 +1,3265 @@ +#include "g_local.h" +#include "bot.h" +#include "g_ctf.h" + +qboolean Pickup_Weapon (edict_t *ent, edict_t *other); +void Use_Weapon (edict_t *ent, gitem_t *inv); +void Use_Weapon2 (edict_t *ent, gitem_t *inv); +void Drop_Weapon (edict_t *ent, gitem_t *inv); +/* +void Weapon_Blaster (edict_t *ent); +void Weapon_Shotgun (edict_t *ent); +void Weapon_SuperShotgun (edict_t *ent); +void Weapon_Machinegun (edict_t *ent); +void Weapon_Chaingun (edict_t *ent); +void Weapon_HyperBlaster (edict_t *ent); +void Weapon_RocketLauncher (edict_t *ent); +void Weapon_Grenade (edict_t *ent); +void Weapon_GrenadeLauncher (edict_t *ent); +void Weapon_Railgun (edict_t *ent); +void Weapon_BFG (edict_t *ent); + +// RAFAEL +void Weapon_Ionripper (edict_t *ent); +void Weapon_Phalanx (edict_t *ent); +void Weapon_Trap (edict_t *ent); +*/ +gitem_armor_t jacketarmor_info = { 25, 50, .30, .00, ARMOR_JACKET}; +gitem_armor_t combatarmor_info = { 50, 100, .60, .30, ARMOR_COMBAT}; +gitem_armor_t bodyarmor_info = {100, 200, .80, .60, ARMOR_BODY}; + +static int jacket_armor_index; +static int combat_armor_index; +static int body_armor_index; +static int power_screen_index; +static int power_shield_index; + +#define HEALTH_IGNORE_MAX 1 +#define HEALTH_TIMED 2 + +void Use_Quad (edict_t *ent, gitem_t *item); +// RAFAEL +void Use_QuadFire (edict_t *ent, gitem_t *item); + +static int quad_drop_timeout_hack; +// RAFAEL +static int quad_fire_drop_timeout_hack; + +//====================================================================== + + +/* +=============== +GetItemByIndex +=============== +*/ +gitem_t *GetItemByIndex (int index) +{ + if (index == 0 || index >= game.num_items) + return NULL; + + return &itemlist[index]; +} + + +/* +=============== +FindItemByClassname + +=============== +*/ +gitem_t *FindItemByClassname (char *classname) +{ + int i; + gitem_t *it; + + it = itemlist; + for (i=0 ; iclassname) + continue; + if (!Q_stricmp(it->classname, classname)) + return it; + } + + return NULL; +} + +/* +=============== +FindItem + +=============== +*/ +gitem_t *FindItem (char *pickup_name) +{ + int i; + gitem_t *it; + + it = itemlist; + for (i=0 ; ipickup_name) + continue; + if (!Q_stricmp(it->pickup_name, pickup_name)) + return it; + } + + return NULL; +} + +//====================================================================== + +void DoRespawn (edict_t *ent) +{ + if (ent->team) + { + edict_t *master; + int count; + int choice; + + master = ent->teammaster; + +//ZOID +//in ctf, when we are weapons stay, only the master of a team of weapons +//is spawned + if (ctf->value && + ((int)dmflags->value & DF_WEAPONS_STAY) && + master->item && (master->item->flags & IT_WEAPON)) + ent = master; + else { +//ZOID + + for (count = 0, ent = master; ent; ent = ent->chain, count++) + ; + + choice = rand() % count; + + for (count = 0, ent = master; count < choice; ent = ent->chain, count++) + ; + } + } + + ent->svflags &= ~SVF_NOCLIENT; + ent->solid = SOLID_TRIGGER; + gi.linkentity (ent); + + if(ent->classname[0] == 'R') return; + + // send an effect + ent->s.event = EV_ITEM_RESPAWN; +} + +void SetRespawn (edict_t *ent, float delay) +{ + ent->flags |= FL_RESPAWN; + ent->svflags |= SVF_NOCLIENT; + ent->solid = SOLID_NOT; + ent->nextthink = level.time + delay; + ent->think = DoRespawn; + gi.linkentity (ent); +} + + +//====================================================================== + +qboolean Pickup_Powerup (edict_t *ent, edict_t *other) +{ + int quantity; + + quantity = other->client->pers.inventory[ITEM_INDEX(ent->item)]; + if ((skill->value == 1 && quantity >= 2) || (skill->value >= 2 && quantity >= 1)) + return false; + + if ((coop->value) && (ent->item->flags & IT_STAY_COOP) && (quantity > 0)) + return false; + + other->client->pers.inventory[ITEM_INDEX(ent->item)]++; + + if (deathmatch->value) + { + if (!(ent->spawnflags & DROPPED_ITEM) ) + SetRespawn (ent, ent->item->quantity); + if (((int)dmflags->value & DF_INSTANT_ITEMS) || ((ent->item->use == Use_Quad) && (ent->spawnflags & DROPPED_PLAYER_ITEM))) + { + if ((ent->item->use == Use_Quad) && (ent->spawnflags & DROPPED_PLAYER_ITEM)) + quad_drop_timeout_hack = (ent->nextthink - level.time) / FRAMETIME; + ent->item->use (other, ent->item); + } + // RAFAEL + else if (((int)dmflags->value & DF_INSTANT_ITEMS) || ((ent->item->use == Use_QuadFire) && (ent->spawnflags & DROPPED_PLAYER_ITEM))) + { + if ((ent->item->use == Use_QuadFire) && (ent->spawnflags & DROPPED_PLAYER_ITEM)) + quad_fire_drop_timeout_hack = (ent->nextthink - level.time) / FRAMETIME; + ent->item->use (other, ent->item); + } + } + + return true; +} + +void Drop_General (edict_t *ent, gitem_t *item) +{ + Drop_Item (ent, item); + ent->client->pers.inventory[ITEM_INDEX(item)]--; + ValidateSelectedItem (ent); +} + +float Get_yaw (vec3_t vec); +//edict_t *GetBotFlag1(); //チーム1の旗 +//edict_t *GetBotFlag2(); //チーム2の旗 +//====================================================================== +qboolean Pickup_Navi (edict_t *ent, edict_t *other) +{ + edict_t *flage,*flagf; + vec3_t v; + int i,j,k; + qboolean flg; + + if (!(ent->spawnflags & DROPPED_ITEM) && (deathmatch->value)) + if( ent->item->quantity && ent->classname[6] != 'F') SetRespawn (ent, ent->item->quantity); + + //on door(up & down) + if( ent->classname[6] == '3' && ent->union_ent) + { + if(ent->target_ent == other /*->client->zc.second_target == ent*/) + { +//gi.bprintf(PRINT_HIGH,"get target!\n"); + other->client->zc.zcstate &= ~STS_WAITS; + other->client->zc.waitin_obj = ent->union_ent; + if(ent->union_ent->spawnflags & PDOOR_TOGGLE) + { + if(ent->union_ent->moveinfo.state == PSTATE_DOWN + || ent->union_ent->moveinfo.state == PSTATE_BOTTOM) other->client->zc.zcstate |= STS_W_ONDOORDWN; + else other->client->zc.zcstate |= STS_W_ONDOORUP; + } + else + { + if(ent->union_ent->moveinfo.state == PSTATE_DOWN + || ent->union_ent->moveinfo.state == PSTATE_TOP) other->client->zc.zcstate |= STS_W_ONDOORDWN; + else if(ent->union_ent->moveinfo.state == PSTATE_UP + || ent->union_ent->moveinfo.state == PSTATE_BOTTOM) other->client->zc.zcstate |= STS_W_ONDOORUP; + } + //j = other->client->zc.routeindex - 10; + //ルートのアップデート + for(i = - MAX_DOORSEARCH; i < MAX_DOORSEARCH ;i++) + { + if(i <= 0) j = other->client->zc.routeindex - (MAX_DOORSEARCH - i) ; + else j = other->client->zc.routeindex + i; + if(j < 0) continue; + if(j >= CurrentIndex) continue; +// if(!Route[j].index) break; + if((Route[j].state == GRS_ONDOOR + && Route[j].ent == ent->union_ent) || Route[j].state == GRS_PUSHBUTTON) + { + k = 1; + flg = false; + while(1) + { + if((j + k) >= CurrentIndex) + { +//gi.bprintf(PRINT_HIGH,"overflow!!!\n"); + break; + } + if((j + k) >= other->client->zc.routeindex) + { + Get_RouteOrigin(j + k,v); + if(fabs(v[2] - other->s.origin[2])> JumpMax) + { + if(0/*Route[j].state == GRS_PUSHBUTTON*/) + { + if(fabs(Route[j].ent->union_ent->s.origin[2] + 8 - other->s.origin[2])< JumpMax) flg = true; + } + else flg = true; +//gi.bprintf(PRINT_HIGH,"hoooo!!!\n"); + break; + } + } + k++; + } + if((j + k) < CurrentIndex && flg) + { +//gi.bprintf(PRINT_HIGH,"set!!!\n"); + other->client->zc.routeindex = j + k; + break; + } + } + //j++; + } + if(!flg) + { + other->client->zc.zcstate |= STS_W_DONT; +// gi.bprintf(PRINT_HIGH,"failed!\n"); + } + ent->target_ent = NULL; + } +//else gi.bprintf(PRINT_HIGH,"not target!\n"); + SetRespawn (ent, 1000000); + ent->solid = SOLID_NOT; + } + //roamnavi やめた + else if( ent->classname[6] == '2') + { + //ルートのアップデート + for(i = 0;i < 10;i++) + { + if((other->client->zc.routeindex + i) >= CurrentIndex) break; + if(!Route[other->client->zc.routeindex + i].index) break; + if(Route[other->client->zc.routeindex + i].state == GRS_PUSHBUTTON + && Route[other->client->zc.routeindex + i].ent == ent->union_ent) + { + other->client->zc.routeindex += i + 1; + break; + } + } + } + + if(!ctf->value || ent->classname[6] != 'F') return true; + //ctf navi + if( ctf->value && ent->classname[6] == 'F') + { + if(other->client->resp.ctf_team == CTF_TEAM1) + { + flage = bot_team_flag2;//GetBotFlag2(); + flagf = bot_team_flag1;//GetBotFlag1(); + if( other->moveinfo.state == CARRIER || other->moveinfo.state == SUPPORTER) + { + if(ent->owner != NULL) other->target_ent = ent->owner; + } + else if(other->moveinfo.state == GETTER) + { + if(flage->solid == SOLID_NOT && flagf->solid != SOLID_NOT) + { + if(ent->owner != NULL) other->target_ent = ent->owner; + } + else + { + if(ent->target_ent != NULL) other->target_ent = ent->target_ent; + } + } + else if( other->moveinfo.state == DEFENDER) + { + if(ent->owner != NULL) other->target_ent = ent->owner; + } + } + else if(other->client->resp.ctf_team == CTF_TEAM2) + { + flage = bot_team_flag1;//GetBotFlag1(); + flagf = bot_team_flag2;//GetBotFlag2(); + if( other->moveinfo.state == CARRIER || other->moveinfo.state == SUPPORTER) + { + if(ent->target_ent != NULL) other->target_ent = ent->target_ent; + } + else if(other->moveinfo.state == GETTER) + { + if(flage->solid == SOLID_NOT && flagf->solid != SOLID_NOT) + { + if(ent->target_ent != NULL) other->target_ent = ent->target_ent; + } + else + { + if(ent->owner != NULL) other->target_ent = ent->owner; + } + } + else if( other->moveinfo.state == DEFENDER) + { + if(ent->target_ent != NULL) other->target_ent = ent->target_ent; + } + } + } + + + return true; +} + +qboolean Pickup_Adrenaline (edict_t *ent, edict_t *other) +{ + if (!deathmatch->value) + other->max_health += 1; + + if (other->health < other->max_health) + other->health = other->max_health; + + if (!(ent->spawnflags & DROPPED_ITEM) && (deathmatch->value)) + SetRespawn (ent, ent->item->quantity); + + return true; +} + +qboolean Pickup_AncientHead (edict_t *ent, edict_t *other) +{ + other->max_health += 2; + + if (!(ent->spawnflags & DROPPED_ITEM) && (deathmatch->value)) + SetRespawn (ent, ent->item->quantity); + + return true; +} + +qboolean Pickup_Bandolier (edict_t *ent, edict_t *other) +{ + gitem_t *item; + int index; + + if (other->client->pers.max_bullets < 250) + other->client->pers.max_bullets = 250; + if (other->client->pers.max_shells < 150) + other->client->pers.max_shells = 150; + if (other->client->pers.max_cells < 250) + other->client->pers.max_cells = 250; + if (other->client->pers.max_slugs < 75) + other->client->pers.max_slugs = 75; + // RAFAEL + if (other->client->pers.max_magslug < 75) + other->client->pers.max_magslug = 75; + + item = Fdi_BULLETS;//FindItem("Bullets"); + if (item) + { + index = ITEM_INDEX(item); + other->client->pers.inventory[index] += item->quantity; + if (other->client->pers.inventory[index] > other->client->pers.max_bullets) + other->client->pers.inventory[index] = other->client->pers.max_bullets; + } + + item = Fdi_SHELLS;//FindItem("Shells"); + if (item) + { + index = ITEM_INDEX(item); + other->client->pers.inventory[index] += item->quantity; + if (other->client->pers.inventory[index] > other->client->pers.max_shells) + other->client->pers.inventory[index] = other->client->pers.max_shells; + } + + if (!(ent->spawnflags & DROPPED_ITEM) && (deathmatch->value)) + SetRespawn (ent, ent->item->quantity); + + return true; +} + +qboolean Pickup_Pack (edict_t *ent, edict_t *other) +{ + gitem_t *item; + int index; + + if (other->client->pers.max_bullets < 300) + other->client->pers.max_bullets = 300; + if (other->client->pers.max_shells < 200) + other->client->pers.max_shells = 200; + if (other->client->pers.max_rockets < 100) + other->client->pers.max_rockets = 100; + if (other->client->pers.max_grenades < 100) + other->client->pers.max_grenades = 100; + if (other->client->pers.max_cells < 300) + other->client->pers.max_cells = 300; + if (other->client->pers.max_slugs < 100) + other->client->pers.max_slugs = 100; + // RAFAEL + if (other->client->pers.max_magslug < 100) + other->client->pers.max_magslug = 100; + + item = Fdi_BULLETS;//FindItem("Bullets"); + if (item) + { + index = ITEM_INDEX(item); + other->client->pers.inventory[index] += item->quantity; + if (other->client->pers.inventory[index] > other->client->pers.max_bullets) + other->client->pers.inventory[index] = other->client->pers.max_bullets; + } + + item = Fdi_SHELLS;//FindItem("Shells"); + if (item) + { + index = ITEM_INDEX(item); + other->client->pers.inventory[index] += item->quantity; + if (other->client->pers.inventory[index] > other->client->pers.max_shells) + other->client->pers.inventory[index] = other->client->pers.max_shells; + } + + item = Fdi_CELLS;//FindItem("Cells"); + if (item) + { + index = ITEM_INDEX(item); + other->client->pers.inventory[index] += item->quantity; + if (other->client->pers.inventory[index] > other->client->pers.max_cells) + other->client->pers.inventory[index] = other->client->pers.max_cells; + } + + if (item) + { + item = Fdi_GRENADES;//FindItem("Grenades"); + index = ITEM_INDEX(item); + other->client->pers.inventory[index] += item->quantity; + if (other->client->pers.inventory[index] > other->client->pers.max_grenades) + other->client->pers.inventory[index] = other->client->pers.max_grenades; + } + + item = Fdi_ROCKETS;//FindItem("Rockets"); + if (item) + { + index = ITEM_INDEX(item); + other->client->pers.inventory[index] += item->quantity; + if (other->client->pers.inventory[index] > other->client->pers.max_rockets) + other->client->pers.inventory[index] = other->client->pers.max_rockets; + } + + item = Fdi_SLUGS;//FindItem("Slugs"); + if (item) + { + index = ITEM_INDEX(item); + other->client->pers.inventory[index] += item->quantity; + if (other->client->pers.inventory[index] > other->client->pers.max_slugs) + other->client->pers.inventory[index] = other->client->pers.max_slugs; + } + + // RAFAEL + item = Fdi_MAGSLUGS;//FindItem ("Mag Slug"); + if (item) + { + index = ITEM_INDEX(item); + other->client->pers.inventory[index] += item->quantity; + if (other->client->pers.inventory[index] > other->client->pers.max_magslug) + other->client->pers.inventory[index] = other->client->pers.max_magslug; + } + + if (!(ent->spawnflags & DROPPED_ITEM) && (deathmatch->value)) + SetRespawn (ent, ent->item->quantity); + + return true; +} + +//====================================================================== + +void Use_Quad (edict_t *ent, gitem_t *item) +{ + int timeout; + + ent->client->pers.inventory[ITEM_INDEX(item)]--; + ValidateSelectedItem (ent); + + if (quad_drop_timeout_hack) + { + timeout = quad_drop_timeout_hack; + quad_drop_timeout_hack = 0; + } + else + { + timeout = 300; + } + + if (ent->client->quad_framenum > level.framenum) + ent->client->quad_framenum += timeout; + else + ent->client->quad_framenum = level.framenum + timeout; + + gi.sound(ent, CHAN_ITEM, gi.soundindex("items/damage.wav"), 1, ATTN_NORM, 0); +} +// ===================================================================== + +// RAFAEL +void Use_QuadFire (edict_t *ent, gitem_t *item) +{ + int timeout; + + ent->client->pers.inventory[ITEM_INDEX(item)]--; + ValidateSelectedItem (ent); + + if (quad_fire_drop_timeout_hack) + { + timeout = quad_fire_drop_timeout_hack; + quad_fire_drop_timeout_hack = 0; + } + else + { + timeout = 300; + } + + if (ent->client->quadfire_framenum > level.framenum) + ent->client->quadfire_framenum += timeout; + else + ent->client->quadfire_framenum = level.framenum + timeout; + + gi.sound(ent, CHAN_ITEM, gi.soundindex("items/quadfire1.wav"), 1, ATTN_NORM, 0); +} +//====================================================================== + +void Use_Breather (edict_t *ent, gitem_t *item) +{ + ent->client->pers.inventory[ITEM_INDEX(item)]--; + ValidateSelectedItem (ent); + + if (ent->client->breather_framenum > level.framenum) + ent->client->breather_framenum += 300; + else + ent->client->breather_framenum = level.framenum + 300; + +// gi.sound(ent, CHAN_ITEM, gi.soundindex("items/damage.wav"), 1, ATTN_NORM, 0); +} + +//====================================================================== + +void Use_Envirosuit (edict_t *ent, gitem_t *item) +{ + ent->client->pers.inventory[ITEM_INDEX(item)]--; + ValidateSelectedItem (ent); + + if (ent->client->enviro_framenum > level.framenum) + ent->client->enviro_framenum += 300; + else + ent->client->enviro_framenum = level.framenum + 300; + +// gi.sound(ent, CHAN_ITEM, gi.soundindex("items/damage.wav"), 1, ATTN_NORM, 0); +} + +//====================================================================== + +void Use_Invulnerability (edict_t *ent, gitem_t *item) +{ + ent->client->pers.inventory[ITEM_INDEX(item)]--; + ValidateSelectedItem (ent); + + if (ent->client->invincible_framenum > level.framenum) + ent->client->invincible_framenum += 300; + else + ent->client->invincible_framenum = level.framenum + 300; + + gi.sound(ent, CHAN_ITEM, gi.soundindex("items/protect.wav"), 1, ATTN_NORM, 0); +} + +//====================================================================== + +void Use_Silencer (edict_t *ent, gitem_t *item) +{ + ent->client->pers.inventory[ITEM_INDEX(item)]--; + ValidateSelectedItem (ent); + ent->client->silencer_shots += 30; + +// gi.sound(ent, CHAN_ITEM, gi.soundindex("items/damage.wav"), 1, ATTN_NORM, 0); +} + +//====================================================================== + +qboolean Pickup_Key (edict_t *ent, edict_t *other) +{ + other->client->pers.inventory[ITEM_INDEX(ent->item)]++; + return true; +} + +//====================================================================== + +qboolean Add_Ammo (edict_t *ent, gitem_t *item, int count) +{ + int index; + int max; + + if (!ent->client) + return false; + + if (item->tag == AMMO_BULLETS) + max = ent->client->pers.max_bullets; + else if (item->tag == AMMO_SHELLS) + max = ent->client->pers.max_shells; + else if (item->tag == AMMO_ROCKETS) + max = ent->client->pers.max_rockets; + else if (item->tag == AMMO_GRENADES) + max = ent->client->pers.max_grenades; + else if (item->tag == AMMO_CELLS) + max = ent->client->pers.max_cells; + else if (item->tag == AMMO_SLUGS) + max = ent->client->pers.max_slugs; + // RAFAEL + else if (item->tag == AMMO_MAGSLUG) + max = ent->client->pers.max_magslug; + // RAFAEL + else if (item->tag == AMMO_TRAP) + max = ent->client->pers.max_trap; + else + return false; + + index = ITEM_INDEX(item); + + if (ent->client->pers.inventory[index] == max) + return false; + + ent->client->pers.inventory[index] += count; + +//ponko + if(chedit->value && ent == &g_edicts[1]) ent->client->pers.inventory[index] = 0; +//ponko + + if (ent->client->pers.inventory[index] > max) + ent->client->pers.inventory[index] = max; + + return true; +} + +qboolean Pickup_Ammo (edict_t *ent, edict_t *other) +{ + int count; + + if (ent->count) + count = ent->count; + else + count = ent->item->quantity; + + if (!Add_Ammo (other, ent->item, count)) + return false; + + if (!(ent->spawnflags & (DROPPED_ITEM | DROPPED_PLAYER_ITEM)) && (deathmatch->value)) + SetRespawn (ent, 30); + return true; +} + +void Drop_Ammo (edict_t *ent, gitem_t *item) +{ + edict_t *dropped; + int index; + + index = ITEM_INDEX(item); + dropped = Drop_Item (ent, item); + if (ent->client->pers.inventory[index] >= item->quantity) + dropped->count = item->quantity; + else + dropped->count = ent->client->pers.inventory[index]; + ent->client->pers.inventory[index] -= dropped->count; + ValidateSelectedItem (ent); +} + + +//====================================================================== + +void MegaHealth_think (edict_t *self) +{ + if (self->owner->health > self->owner->max_health +//ZOID + && !CTFHasRegeneration(self->owner) +//ZOID + ) + { + self->nextthink = level.time + 1; + self->owner->health -= 1; + return; + } + + if (!(self->spawnflags & DROPPED_ITEM) && (deathmatch->value)) + SetRespawn (self, 20); + else + G_FreeEdict (self); +} + +qboolean Pickup_Health (edict_t *ent, edict_t *other) +{ + if (!(ent->style & HEALTH_IGNORE_MAX)) + if (other->health >= other->max_health) + return false; + +//ZOID + if (other->health >= 250 && ent->count > 25) + return false; +//ZOID + + other->health += ent->count; + +//ZOID + if (other->health > 250 && ent->count > 25) + other->health = 250; +//ZOID + + if (ent->count == 2) + ent->item->pickup_sound = "items/s_health.wav"; + else if (ent->count == 10) + ent->item->pickup_sound = "items/n_health.wav"; + else if (ent->count == 25) + ent->item->pickup_sound = "items/l_health.wav"; + else // (ent->count == 100) + ent->item->pickup_sound = "items/m_health.wav"; + + if (!(ent->style & HEALTH_IGNORE_MAX)) + { + if (other->health > other->max_health) + other->health = other->max_health; + } +//ZOID + if ((ent->style & HEALTH_TIMED) + && !CTFHasRegeneration(other) +//ZOID + ) + { + ent->think = MegaHealth_think; + ent->nextthink = level.time + 5; + ent->owner = other; + ent->flags |= FL_RESPAWN; + ent->svflags |= SVF_NOCLIENT; + ent->solid = SOLID_NOT; + } + else + { + if (!(ent->spawnflags & DROPPED_ITEM) && (deathmatch->value)) + SetRespawn (ent, 30); + } + + return true; +} + +//====================================================================== + +int ArmorIndex (edict_t *ent) +{ + if (!ent->client) + return 0; + + if (ent->client->pers.inventory[jacket_armor_index] > 0) + return jacket_armor_index; + + if (ent->client->pers.inventory[combat_armor_index] > 0) + return combat_armor_index; + + if (ent->client->pers.inventory[body_armor_index] > 0) + return body_armor_index; + + return 0; +} + +qboolean Pickup_Armor (edict_t *ent, edict_t *other) +{ + int old_armor_index; + gitem_armor_t *oldinfo; + gitem_armor_t *newinfo; + int newcount; + float salvage; + int salvagecount; + + // get info on new armor + newinfo = (gitem_armor_t *)ent->item->info; + + old_armor_index = ArmorIndex (other); + + // handle armor shards specially + if (ent->item->tag == ARMOR_SHARD) + { + if (!old_armor_index) + other->client->pers.inventory[jacket_armor_index] = 2; + else + other->client->pers.inventory[old_armor_index] += 2; + } + + // if player has no armor, just use it + else if (!old_armor_index) + { + other->client->pers.inventory[ITEM_INDEX(ent->item)] = newinfo->base_count; + } + + // use the better armor + else + { + // get info on old armor + if (old_armor_index == jacket_armor_index) + oldinfo = &jacketarmor_info; + else if (old_armor_index == combat_armor_index) + oldinfo = &combatarmor_info; + else // (old_armor_index == body_armor_index) + oldinfo = &bodyarmor_info; + + if (newinfo->normal_protection > oldinfo->normal_protection) + { + // calc new armor values + salvage = oldinfo->normal_protection / newinfo->normal_protection; + salvagecount = salvage * other->client->pers.inventory[old_armor_index]; + newcount = newinfo->base_count + salvagecount; + if (newcount > newinfo->max_count) + newcount = newinfo->max_count; + + // zero count of old armor so it goes away + other->client->pers.inventory[old_armor_index] = 0; + + // change armor to new item with computed value + other->client->pers.inventory[ITEM_INDEX(ent->item)] = newcount; +//ponko + if(chedit->value && other == &g_edicts[1]) + other->client->pers.inventory[ITEM_INDEX(ent->item)] = 0; +//ponko + } + else + { + // calc new armor values + salvage = newinfo->normal_protection / oldinfo->normal_protection; + salvagecount = salvage * newinfo->base_count; + newcount = other->client->pers.inventory[old_armor_index] + salvagecount; + if (newcount > oldinfo->max_count) + newcount = oldinfo->max_count; + + // if we're already maxed out then we don't need the new armor + if (other->client->pers.inventory[old_armor_index] >= newcount) + return false; + + // update current armor value + other->client->pers.inventory[old_armor_index] = newcount; +//ponko + if(chedit->value && other == &g_edicts[1]) + other->client->pers.inventory[old_armor_index] = 0; +//ponko + } + } + + if (!(ent->spawnflags & DROPPED_ITEM) && (deathmatch->value)) + SetRespawn (ent, 20); + + return true; +} + +//====================================================================== + +int PowerArmorType (edict_t *ent) +{ + if (!ent->client) + return POWER_ARMOR_NONE; + + if (!(ent->flags & FL_POWER_ARMOR)) + return POWER_ARMOR_NONE; + + if (ent->client->pers.inventory[power_shield_index] > 0) + return POWER_ARMOR_SHIELD; + + if (ent->client->pers.inventory[power_screen_index] > 0) + return POWER_ARMOR_SCREEN; + + return POWER_ARMOR_NONE; +} + +void Use_PowerArmor (edict_t *ent, gitem_t *item) +{ + int index; + + if (ent->flags & FL_POWER_ARMOR) + { + ent->flags &= ~FL_POWER_ARMOR; + gi.sound(ent, CHAN_ITEM, gi.soundindex("misc/power2.wav"), 1, ATTN_NORM, 0); //FIXME powering down sound + } + else + { + index = ITEM_INDEX(Fdi_CELLS/*FindItem("Cells")*/); + if (!ent->client->pers.inventory[index] && !(ent->svflags & SVF_MONSTER)) + { + gi.cprintf (ent, PRINT_HIGH, "No cells for power armor.\n"); + return; + } + ent->flags |= FL_POWER_ARMOR; + gi.sound(ent, CHAN_ITEM, gi.soundindex("misc/power1.wav"), 1, ATTN_NORM, 0); //FIXME powering up sound + } +} + +qboolean Pickup_PowerArmor (edict_t *ent, edict_t *other) +{ + int quantity; + + quantity = other->client->pers.inventory[ITEM_INDEX(ent->item)]; + + other->client->pers.inventory[ITEM_INDEX(ent->item)]++; + +//ponko + if(chedit->value && other == &g_edicts[1]) + other->client->pers.inventory[ITEM_INDEX(ent->item)] = 0; +//ponko + + if (deathmatch->value) + { + if (!(ent->spawnflags & DROPPED_ITEM) ) + SetRespawn (ent, ent->item->quantity); + // auto-use for DM only if we didn't already have one + if (!quantity) + ent->item->use (other, ent->item); + } + + return true; +} + +void Drop_PowerArmor (edict_t *ent, gitem_t *item) +{ + if ((ent->flags & FL_POWER_ARMOR) && (ent->client->pers.inventory[ITEM_INDEX(item)] == 1)) + Use_PowerArmor (ent, item); + Drop_General (ent, item); +} +//====================================================================== + +/* +=============== +Touch_Item +=============== +*/ +void Touch_Item (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + int k; + + //don't pickup tech when chaining + if (ctf->value && chedit->value) + { + if(ent->classname[5] =='t') return; + } + if (strcmp(other->classname, "player")) + return; + if (ent->classname[0] == 'R') + { + if(!(other->svflags & SVF_MONSTER)) return; + if(ent->classname[6] == 'F'&& other->target_ent != NULL) + { + if(other->target_ent != ent) return; +// else if(other->moveinfo.state == SUPPORTER) return; + } + } + if (other->health < 1) + return; // dead people can't pickup + if (!ent->item->pickup) + return; // not a grabbable item? + if (!ent->item->pickup(ent, other)) + return; // player can't hold it + + if (!(other->svflags & SVF_MONSTER)) + { + + // flash the screen + other->client->bonus_alpha = 0.25; + + // show icon and name on status bar + other->client->ps.stats[STAT_PICKUP_ICON] = gi.imageindex(ent->item->icon); + other->client->ps.stats[STAT_PICKUP_STRING] = CS_ITEMS+ITEM_INDEX(ent->item); + other->client->pickup_msg_time = level.time + 3.0; + + // change selected item + if (ent->item->use) + other->client->pers.selected_item = other->client->ps.stats[STAT_SELECTED_ITEM] = ITEM_INDEX(ent->item); + + } + else + { + // change selected item + if (ent->item->use) + { + k = Get_KindWeapon(ent->item); + if(k > WEAP_BLASTER) + { + if(Bot[other->client->zc.botindex].param[BOP_PRIWEP] == k) ent->item->use(other,ent->item); + else if(k != Get_KindWeapon(other->client->pers.weapon)) + { + if(Bot[other->client->zc.botindex].param[BOP_SECWEP] == k) ent->item->use(other,ent->item); + } + } + + + } + } + if(ent->classname[0] != 'R') + { + gi.sound(other, CHAN_ITEM, gi.soundindex(ent->item->pickup_sound), 1, ATTN_NORM, 0); + PlayerNoise(ent, ent->s.origin, PNOISE_SELF); //ponko + G_UseTargets (ent, other); + } +// else gi.bprintf(PRINT_HIGH,"get %s %x inv %i!\n",ent->classname,ent->spawnflags,other->client->pers.inventory[ITEM_INDEX(ent->item)]); + + k = false; + //flag set ファンクションの上にある場合は無視 + if(ent->groundentity) if(ent->groundentity->union_ent) k = true; + + //route update + if(!k && chedit->value && CurrentIndex < MAXNODES && other == &g_edicts[1]) + { + if((ent->classname[0] == 'w' + || (ent->classname[0] =='i' && + (ent->classname[5] == 'q' + || ent->classname[5] =='t' + || ent->classname[5] =='f' + || ent->classname[5] =='i' + || ent->classname[5] =='p' + || ent->classname[5] =='s' + || ent->classname[5] =='b' + || ent->classname[5] =='e' + || ent->classname[5] =='a')) + || (ent->classname[0] =='i' && ent->classname[5] =='h' && ent->classname[12] =='m') + || (ent->classname[0] =='a') + ) + && !(ent->spawnflags & (DROPPED_ITEM | DROPPED_PLAYER_ITEM))) + { +// gi.bprintf(PRINT_HIGH,"woohoo!\n"); + VectorCopy(/*ent->s.origin*/ent->monsterinfo.last_sighting,Route[CurrentIndex].Pt); + Route[CurrentIndex].ent = ent; + if(ent == bot_team_flag1) { Route[CurrentIndex].state = GRS_REDFLAG;/*gi.bprintf(PRINT_HIGH,"woohoo!\n");*/} + else if(ent == bot_team_flag2) { Route[CurrentIndex].state = GRS_BLUEFLAG;/*gi.bprintf(PRINT_HIGH,"woohoo!\n");*/} + else Route[CurrentIndex].state = GRS_ITEMS; + if(++CurrentIndex < MAXNODES) + { + gi.bprintf(PRINT_HIGH,"Last %i pod(s).\n",MAXNODES - CurrentIndex); + memset(&Route[CurrentIndex],0,sizeof(route_t)); + Route[CurrentIndex].index = Route[CurrentIndex - 1].index +1; + } + } + } + + //respawn set + if (ent->flags & FL_RESPAWN) + ent->flags &= ~FL_RESPAWN; + else if(ent->classname[6] != 'F') G_FreeEdict (ent); +} + +//====================================================================== + +static void drop_temp_touch (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + if (other == ent->owner) + return; + + Touch_Item (ent, other, plane, surf); +} + +static void drop_make_touchable (edict_t *ent) +{ + ent->touch = Touch_Item; + if (deathmatch->value) + { + ent->nextthink = level.time + 29; + ent->think = G_FreeEdict; + } +} + +edict_t *Drop_Item (edict_t *ent, gitem_t *item) +{ + edict_t *dropped; + vec3_t forward, right; + vec3_t offset; + + dropped = G_Spawn(); + + dropped->classname = item->classname; + dropped->item = item; + dropped->spawnflags = DROPPED_ITEM; + dropped->s.effects = item->world_model_flags; + dropped->s.renderfx = RF_GLOW; + VectorSet (dropped->mins, -15, -15, -15); + VectorSet (dropped->maxs, 15, 15, 15); + gi.setmodel (dropped, dropped->item->world_model); + dropped->solid = SOLID_TRIGGER; + dropped->movetype = MOVETYPE_TOSS; + dropped->touch = drop_temp_touch; + dropped->owner = ent; + + if (ent->client) + { + trace_t trace; + + AngleVectors (ent->client->v_angle, forward, right, NULL); + VectorSet(offset, 24, 0, -16); + G_ProjectSource (ent->s.origin, offset, forward, right, dropped->s.origin); + trace = gi.trace (ent->s.origin, dropped->mins, dropped->maxs, + dropped->s.origin, ent, CONTENTS_SOLID); + VectorCopy (trace.endpos, dropped->s.origin); + } + else + { + AngleVectors (ent->s.angles, forward, right, NULL); + VectorCopy (ent->s.origin, dropped->s.origin); + } + + VectorScale (forward, 100, dropped->velocity); + dropped->velocity[2] = 300; + + dropped->think = drop_make_touchable; + dropped->nextthink = level.time + 1; + + gi.linkentity (dropped); + + return dropped; +} + +void Use_Item (edict_t *ent, edict_t *other, edict_t *activator) +{ + ent->svflags &= ~SVF_NOCLIENT; + ent->use = NULL; + + if (ent->spawnflags & 2) // NO_TOUCH + { + ent->solid = SOLID_BBOX; + ent->touch = NULL; + } + else + { + ent->solid = SOLID_TRIGGER; + ent->touch = Touch_Item; + } + + gi.linkentity (ent); +} + +//====================================================================== + +/* +================ +droptofloor +================ +*/ +void droptofloor (edict_t *ent) +{ + vec3_t trmin,trmax,min,mins,maxs; + float i,j,k,yaw; + + gitem_t *it; //j + edict_t *it_ent; //j + + trace_t tr,trx; + vec3_t dest; + float *v; + + v = tv(-15,-15,-15); + VectorCopy (v, ent->mins); + v = tv(15,15,15); + VectorCopy (v, ent->maxs); + + if (ent->model) + gi.setmodel (ent, ent->model); + else + gi.setmodel (ent, ent->item->world_model); + if(ent->classname[6] == 'F') ent->s.modelindex = 0; //かくせ + + ent->solid = SOLID_TRIGGER; + ent->movetype = MOVETYPE_TOSS; + ent->touch = Touch_Item; + + v = tv(0,0,-128); + VectorAdd (ent->s.origin, v, dest); + + tr = gi.trace (ent->s.origin, ent->mins, ent->maxs, dest, ent, MASK_SOLID); + if (tr.startsolid && !chedit->value && ent->classname[6] != 'F') + { +// gi.dprintf ("droptofloor: %s startsolid at %s\n", ent->classname, vtos(ent->s.origin)); + // RAFAEL + if (strcmp (ent->classname, "foodcube") == 0) + { + VectorCopy (ent->s.origin, tr.endpos); + ent->velocity[2] = 0; + } + else + { +// gi.dprintf ("droptofloor: %s startsolid at %s\n", ent->classname, vtos(ent->s.origin)); + G_FreeEdict (ent); + return; + } + } + + VectorCopy (tr.endpos, ent->s.origin); + + if (ent->team) + { + ent->flags &= ~FL_TEAMSLAVE; + ent->chain = ent->teamchain; + ent->teamchain = NULL; + + ent->svflags |= SVF_NOCLIENT; + ent->solid = SOLID_NOT; + if (ent == ent->teammaster) + { + ent->nextthink = level.time + FRAMETIME; + ent->think = DoRespawn; + } + } + + if (ent->spawnflags & 2) // NO_TOUCH + { + ent->solid = SOLID_BBOX; + ent->touch = NULL; + ent->s.effects &= ~EF_ROTATE; + ent->s.renderfx &= ~RF_GLOW; + } + + if (ent->spawnflags & 1) // TRIGGER_SPAWN + { + ent->svflags |= SVF_NOCLIENT; + ent->solid = SOLID_NOT; + ent->use = Use_Item; + } + + gi.linkentity (ent); + + if(ent->spawnflags & (DROPPED_ITEM | DROPPED_PLAYER_ITEM)) return; + +// if(ctf->value && chedit->value) ent->moveinfo.speed = 0; + + if(ent->classname[0] == 'w' || ent->classname[0] == 'i' || ent->classname[0] == 'a') + { + k = 0; + VectorCopy(ent->s.origin,min); + VectorSet (mins, -16, -16, -16); + VectorSet (maxs, 16, 16, 16); + min[2] -= 128; + for(i = 0 ; i < 8;i++) + { + if(i < 4 ) + { + yaw = 90 * i -180; + yaw = yaw * M_PI * 2 / 360; + for( j = 32 ; j < 100 ; j +=2 ) + { + trmin[0] = ent->s.origin[0] + cos(yaw) * j; + trmin[1] = ent->s.origin[1] + sin(yaw) * j; + trmin[2] = ent->s.origin[2]; + VectorCopy(trmin,trmax); + trmax[2] -= 128; + tr = gi.trace (trmin, mins, maxs, trmax,ent, MASK_PLAYERSOLID ); + trx = gi.trace (trmin, mins, maxs, trmax,ent, CONTENTS_WATER ); + VectorCopy(tr.endpos,trmax); + //trmax[2] += 16; + if( 0/*(trmin[2] - trx.endpos[2]) <= 39 + &&ent->classname[0] == 'w' && k == 0 + && !(gi.pointcontents (trmin) & CONTENTS_WATER) + && (gi.pointcontents (trmax) & CONTENTS_WATER)*/) + { + it = FindItem("Roam Navi"); + it_ent = G_Spawn(); + it_ent->classname = it->classname; + trmin[0] = ent->s.origin[0] + cos(yaw) * (j + trmin[2] - tr.endpos[2] + 100); + trmin[1] = ent->s.origin[1] + sin(yaw) * (j + trmin[2] - tr.endpos[2] + 100); + trmin[2] = trmax[2]+8; + trx = gi.trace (trmax, mins, maxs, trmin,ent, CONTENTS_WATER ); + VectorCopy(trx.endpos,it_ent->s.origin); + SpawnItem3 (it_ent, it); + k = -1; + } + if(tr.endpos[2] < ent->s.origin[2] - 16 + && tr.endpos[2] > min[2] && !tr.allsolid && !tr.startsolid) + { + min[2] = tr.endpos[2]; + min[0] = ent->s.origin[0] + cos(yaw) * (j + 16); + min[1] = ent->s.origin[1] + sin(yaw) * (j + 16); + break; + } + } + } + else + { + yaw = 90 * (i - 4) -135; + yaw = yaw * M_PI * 2 / 360; + for( j = 32 ; j < 80 ; j +=2 ) + { + trmin[0] = ent->s.origin[0] + cos(yaw) *46; + trmin[1] = ent->s.origin[1] + sin(yaw) *46; + trmin[2] = ent->s.origin[2]; + VectorCopy(trmin,trmax); + trmax[2] -= 128; + tr = gi.trace (trmin, NULL, NULL, trmax,ent, MASK_PLAYERSOLID ); + if(tr.endpos[2] < ent->s.origin[2] - 16 && tr.endpos[2] > min[2] && !tr.allsolid && !tr.startsolid) + { + VectorCopy(tr.endpos,min); + break; + } + } + } + } + VectorCopy(min,ent->moveinfo.start_origin); + } +} + +/* +================ +droptofloor2 +================ +*/ +void droptofloor2 (edict_t *ent) +{ + vec3_t trmin,trmax,min,mins,maxs; + float i,j,yaw; + + trace_t tr; + vec3_t dest; + float *v; + + v = tv(-15,-15,-15); + VectorCopy (v, ent->mins); + v = tv(8,8,15); + VectorCopy (v, ent->maxs); +///////// + if(ent->union_ent && Q_stricmp (ent->classname,"R_navi2")) //2は移動なし + { +// dest[0] = (ent->union_ent->s.origin[0] + ent->union_ent->mins[0] + ent->union_ent->s.origin[0] + ent->union_ent->maxs[0])/2;//ent->s.origin[0]; +// dest[1] = (ent->union_ent->s.origin[1] + ent->union_ent->mins[1] + ent->union_ent->s.origin[1] + ent->union_ent->maxs[1])/2; + + dest[0] = (ent->union_ent->s.origin[0] + ent->union_ent->mins[0] + ent->union_ent->s.origin[0] + ent->union_ent->maxs[0])/2;//ent->s.origin[0]; + dest[1] = (ent->union_ent->s.origin[1] + ent->union_ent->mins[1] + ent->union_ent->s.origin[1] + ent->union_ent->maxs[1])/2; + + j = 0; + for( i = ent->union_ent->s.origin[2] + ent->union_ent->mins[2] /*moveinfo.start_origin[2]+15*/ + ; i <= ent->union_ent->s.origin[2] + ent->union_ent->maxs[2] +16/*ent->moveinfo.end_origin[2]+16*/ + ; i++) + { + dest[2] = i; + tr = gi.trace(dest,ent->mins,ent->maxs,dest,ent,MASK_SOLID); // | MASK_WATER); + if((!tr.startsolid && !tr.allsolid) && j == 1) + { + j = 2; + break; + } + else if((tr.startsolid || tr.allsolid) && j == 0 && tr.ent == ent->union_ent) j = 1; + + } + VectorCopy (dest,ent->s.origin); + VectorSubtract(ent->s.origin,ent->union_ent->s.origin,ent->moveinfo.dir); + } +////////// +/* if (ent->model) + gi.setmodel (ent, ent->model); + else + gi.setmodel (ent, ent->item->world_model);*/ + ent->s.modelindex = 0; //かくせ! +//ent->s.modelindex =gi.modelindex ("models/items/armor/body/tris.md2"); + if(Q_stricmp (ent->classname,"R_navi3") == 0) ent->solid = SOLID_NOT; + else ent->solid = SOLID_TRIGGER; + ent->movetype = MOVETYPE_TOSS; + ent->touch = Touch_Item; + ent->use = NULL; + + v = tv(0,0,-128); + VectorAdd (ent->s.origin, v, dest); + + tr = gi.trace (ent->s.origin, ent->mins, ent->maxs, dest, ent, MASK_SOLID); + if (tr.startsolid && ent->classname[0] != 'R' && ent->classname[6] != 'X') + { +// gi.dprintf ("droptofloor: %s startsolid at %s\n", ent->classname, vtos(ent->s.origin)); + G_FreeEdict (ent); + return; + } + + VectorCopy (tr.endpos, ent->s.origin); + + if (ent->team) + { + ent->flags &= ~FL_TEAMSLAVE; + ent->chain = ent->teamchain; + ent->teamchain = NULL; + + ent->svflags |= SVF_NOCLIENT; + ent->solid = SOLID_NOT; + if (ent == ent->teammaster) + { + ent->nextthink = level.time + FRAMETIME; + ent->think = DoRespawn; + } + } + + if (ent->spawnflags & 2) // NO_TOUCH + { + ent->solid = SOLID_BBOX; + ent->touch = NULL; + ent->s.effects &= ~EF_ROTATE; + ent->s.renderfx &= ~RF_GLOW; + } + + if (ent->spawnflags & 1) // TRIGGER_SPAWN + { + ent->svflags |= SVF_NOCLIENT; + ent->solid = SOLID_NOT; + ent->use = Use_Item; + } + + gi.linkentity (ent); + + if(1) + { + VectorCopy(ent->s.origin,min); + VectorSet (mins, -15, -15, -15); + VectorSet (maxs, 8, 8, 0); + min[2] -= 128; + for(i = 0 ; i < 8;i++) + { + if(i < 4 ) + { + yaw = 90 * i -180; + yaw = yaw * M_PI * 2 / 360; + for( j = 32 ; j < 80 ; j +=2 ) + { + trmin[0] = ent->s.origin[0] + cos(yaw) * j; + trmin[1] = ent->s.origin[1] + sin(yaw) * j; + trmin[2] = ent->s.origin[2]; + VectorCopy(trmin,trmax); + trmax[2] -= 128; + tr = gi.trace (trmin, mins, maxs, trmax,ent, MASK_PLAYERSOLID ); + if(tr.endpos[2] < ent->s.origin[2] - 16 && tr.endpos[2] > min[2] && !tr.allsolid && !tr.startsolid) + { + min[2] = tr.endpos[2]; + min[0] = ent->s.origin[0] + cos(yaw) * (j + 16); + min[1] = ent->s.origin[1] + sin(yaw) * (j + 16); + break; + } + } + } + else + { + yaw = 90 * (i - 4) -135; + yaw = yaw * M_PI * 2 / 360; + for( j = 32 ; j < 80 ; j +=2 ) + { + trmin[0] = ent->s.origin[0] + cos(yaw) *46; + trmin[1] = ent->s.origin[1] + sin(yaw) *46; + trmin[2] = ent->s.origin[2]; + VectorCopy(trmin,trmax); + trmax[2] -= 128; + tr = gi.trace (trmin, NULL, NULL, trmax,ent, MASK_PLAYERSOLID ); + if(tr.endpos[2] < ent->s.origin[2] - 16 && tr.endpos[2] > min[2] && !tr.allsolid && !tr.startsolid) + { + VectorCopy(tr.endpos,min); + break; + } + } + } + } + VectorCopy(min,ent->moveinfo.start_origin); + } +} + +/* +=============== +PrecacheItem + +Precaches all data needed for a given item. +This will be called for each item spawned in a level, +and for each item in each client's inventory. +=============== +*/ +void PrecacheItem (gitem_t *it) +{ + char *s, *start; + char data[MAX_QPATH]; + int len; + gitem_t *ammo; + + if (!it) + return; + + if (it->pickup_sound) + gi.soundindex (it->pickup_sound); + if (it->world_model) + gi.modelindex (it->world_model); + if (it->view_model) + gi.modelindex (it->view_model); + if (it->icon) + gi.imageindex (it->icon); + + // parse everything for its ammo + if (it->ammo && it->ammo[0]) + { + ammo = FindItem (it->ammo); + if (ammo != it) + PrecacheItem (ammo); + } + + // parse the space seperated precache string for other items + s = it->precaches; + if (!s || !s[0]) + return; + + while (*s) + { + start = s; + while (*s && *s != ' ') + s++; + + len = s-start; + if (len >= MAX_QPATH || len < 5) + gi.error ("PrecacheItem: %s has bad precache string", it->classname); + memcpy (data, start, len); + data[len] = 0; + if (*s) + s++; + + // determine type based on extension + if (!strcmp(data+len-3, "md2")) + gi.modelindex (data); + else if (!strcmp(data+len-3, "sp2")) + gi.modelindex (data); + else if (!strcmp(data+len-3, "wav")) + gi.soundindex (data); + if (!strcmp(data+len-3, "pcx")) + gi.imageindex (data); + } +} + + +void ZIGFlagThink(edict_t *ent) +{ + static unsigned short count; + int i; + + count ++; + + if(count > 4) + { + edict_t *tre; + vec3_t v,vv; + + i = gi.pointcontents (ent->s.origin); + if(i & MASK_OPAQUE) + { + SelectSpawnPoint (ent, v, vv); + VectorCopy (v, ent->s.origin); + } + for ( i=maxclients->value+1 ; iinuse || tre->deadflag) continue; + + if(tre->classname[0] == 'p' && tre->movetype != MOVETYPE_NOCLIP && tre->client) + { + if(tre->client->zc.second_target) continue; + VectorSubtract(tre->s.origin,ent->s.origin,v); + if(VectorLength(v) < 350 && Bot_traceS(ent,tre) && v[2] < -JumpMax ) + { + tre->client->zc.second_target = ent; + } + } + } + count = 0; + } + + ent->owner = NULL; + ent->s.frame = 173 + (((ent->s.frame - 173) + 1) % 16); + ent->nextthink = level.time + FRAMETIME; +} + +qboolean ZIGDrop_Flag(edict_t *ent, gitem_t *item) +{ + edict_t *tech; + + if(zflag_ent) return false; + + tech = Drop_Item(ent, item); + tech->nextthink = level.time + FRAMETIME * 10;//level.time + CTF_TECH_TIMEOUT; + tech->think = ZIGFlagThink;//TechThink; + if(ent->client) ent->client->pers.inventory[ITEM_INDEX(item)] = 0; + /*ent*/tech->s.frame = 173; + zflag_ent = tech; + tech->inuse = true; + return true; +} + +qboolean ZIGPickup_Flag (edict_t *ent, edict_t *other) +{ +// gitem_t *item; + +// other->client->pers.selected_item = ITEM_INDEX(item); + zflag_ent = NULL; + other->client->pers.inventory[ITEM_INDEX(zflag_item)] = 1; +// other->client->resp.score += 1; + other->s.modelindex3 = gi.modelindex("models/zflag.md2"/*item->world_model*/); + return true; +} + +/* +============ +SpawnItem + +Sets the clipping size and plants the object on the floor. + +Items can't be immediately dropped to floor, because they might +be on an entity that hasn't spawned yet. +============ +*/ +void SetBotFlag1(edict_t *ent); //チーム1の旗 +void SetBotFlag2(edict_t *ent); //チーム2の旗 + +void SpawnItem (edict_t *ent, gitem_t *item) +{ + PrecacheItem (item); + + if (ent->spawnflags) + { + if (strcmp(ent->classname, "key_power_cube") != 0) + { + ent->spawnflags = 0; + gi.dprintf("%s at %s has invalid spawnflags set\n", ent->classname, vtos(ent->s.origin)); + } + } + + // some items will be prevented in deathmatch + if (deathmatch->value) + { + if ( (int)dmflags->value & DF_NO_ARMOR ) + { + if (item->pickup == Pickup_Armor || item->pickup == Pickup_PowerArmor) + { + G_FreeEdict (ent); + return; + } + } + if ( (int)dmflags->value & DF_NO_ITEMS ) + { + if (item->pickup == Pickup_Powerup) + { + G_FreeEdict (ent); + return; + } + } + if ( (int)dmflags->value & DF_NO_HEALTH ) + { + if (item->pickup == Pickup_Health || item->pickup == Pickup_Adrenaline || item->pickup == Pickup_AncientHead) + { + G_FreeEdict (ent); + return; + } + } + if ( (int)dmflags->value & DF_INFINITE_AMMO ) + { + if ( (item->flags == IT_AMMO) || (strcmp(ent->classname, "weapon_bfg") == 0) ) + { + G_FreeEdict (ent); + return; + } + } + } + + if (coop->value && (strcmp(ent->classname, "key_power_cube") == 0)) + { + ent->spawnflags |= (1 << (8 + level.power_cubes)); + level.power_cubes++; + } + + // don't let them drop items that stay in a coop game + if ((coop->value) && (item->flags & IT_STAY_COOP)) + { + item->drop = NULL; + } + +//ZOID +//Don't spawn the flags unless enabled + if (!ctf->value && + (strcmp(ent->classname, "item_flag_team1") == 0 || + strcmp(ent->classname, "item_flag_team2") == 0)) { + G_FreeEdict(ent); + return; + } +//ZOID + + ent->item = item; + ent->nextthink = level.time + 2 * FRAMETIME; // items start after other solids + ent->think = droptofloor; + ent->s.effects = item->world_model_flags; + ent->s.renderfx = RF_GLOW; + if (ent->model) + gi.modelindex (ent->model); + + VectorCopy(ent->s.origin,ent->monsterinfo.last_sighting); +//ZOID +//flags are server animated and have special handling + if (strcmp(ent->classname, "item_flag_team1") == 0 || + strcmp(ent->classname, "item_flag_team2") == 0) { + ent->think = CTFFlagSetup; + } +//ZOID + +} + +void SpawnItem3 (edict_t *ent, gitem_t *item) +{ + + // PrecacheItem (item); + + ent->item = item; + ent->nextthink = level.time + 2 * FRAMETIME; // items start after other solids + ent->think = droptofloor2; + ent->s.effects = 0; + ent->s.renderfx = 0; + ent->s.modelindex = 0; +// if (ent->model) +// gi.modelindex (0/*ent->model*/); +} + +//====================================================================== + +gitem_t itemlist[] = +{ + { + NULL + }, // leave index 0 alone + + // + // NAVI + // + +/*QUAKED r_navi (.3 .3 1) (-16 -16 -16) (16 16 16) 0 +*/ + { + "R_naviF", + Pickup_Navi, + NULL, + NULL, + NULL, + NULL, + "models/items/armor/body/tris.md2", 0, + NULL, +/* icon */ NULL, +/* pickup */ "Roam NaviF", +/* width */ 2, + 5, + NULL, + 0, + NULL, + 0, +/* precache */ "" + }, + { + "R_naviX", + Pickup_Navi, + NULL, + NULL, + NULL, + NULL, + "models/items/armor/body/tris.md2", 0, + NULL, +/* icon */ NULL, +/* pickup */ "Roam Navi", +/* width */ 2, + 10, + NULL, + 0, + NULL, + 0, +/* precache */ "" + }, + { + "R_navi2", + Pickup_Navi, + NULL, + NULL, + NULL, + NULL, + "models/items/armor/body/tris.md2", 0, + NULL, +/* icon */ NULL, +/* pickup */ "Roam Navi2", +/* width */ 2, + 30, + NULL, + 0, + NULL, + 0, +/* precache */ "" + }, + { + "R_navi3", + Pickup_Navi, + NULL, + NULL, + NULL, + NULL, + "models/items/armor/body/tris.md2", 0, + NULL, +/* icon */ NULL, +/* pickup */ "Roam Navi3", +/* width */ 2, + 20, + NULL, + 0, + NULL, + 0, +/* precache */ "" + }, + // + // ARMOR + // + +/*QUAKED item_armor_body (.3 .3 1) (-16 -16 -16) (16 16 16) 0 +*/ + { + "item_armor_body", + Pickup_Armor, + NULL, + NULL, + NULL, + "misc/ar1_pkup.wav", + "models/items/armor/body/tris.md2", EF_ROTATE, + NULL, +/* icon */ "i_bodyarmor", +/* pickup */ "Body Armor", +/* width */ 3, + 0, + NULL, + IT_ARMOR, + &bodyarmor_info, + ARMOR_BODY, +/* precache */ "" + }, + +/*QUAKED item_armor_combat (.3 .3 1) (-16 -16 -16) (16 16 16) 1 +*/ + { + "item_armor_combat", + Pickup_Armor, + NULL, + NULL, + NULL, + "misc/ar1_pkup.wav", + "models/items/armor/combat/tris.md2", EF_ROTATE, + NULL, +/* icon */ "i_combatarmor", +/* pickup */ "Combat Armor", +/* width */ 3, + 0, + NULL, + IT_ARMOR, + &combatarmor_info, + ARMOR_COMBAT, +/* precache */ "" + }, + +/*QUAKED item_armor_jacket (.3 .3 1) (-16 -16 -16) (16 16 16) 2 +*/ + { + "item_armor_jacket", + Pickup_Armor, + NULL, + NULL, + NULL, + "misc/ar1_pkup.wav", + "models/items/armor/jacket/tris.md2", EF_ROTATE, + NULL, +/* icon */ "i_jacketarmor", +/* pickup */ "Jacket Armor", +/* width */ 3, + 0, + NULL, + IT_ARMOR, + &jacketarmor_info, + ARMOR_JACKET, +/* precache */ "" + }, + +/*QUAKED item_armor_shard (.3 .3 1) (-16 -16 -16) (16 16 16) 3 +*/ + { + "item_armor_shard", + Pickup_Armor, + NULL, + NULL, + NULL, + "misc/ar2_pkup.wav", + "models/items/armor/shard/tris.md2", EF_ROTATE, + NULL, +/* icon */ "i_jacketarmor", +/* pickup */ "Armor Shard", +/* width */ 3, + 0, + NULL, + IT_ARMOR, + NULL, + ARMOR_SHARD, +/* precache */ "" + }, + + +/*QUAKED item_power_screen (.3 .3 1) (-16 -16 -16) (16 16 16) 4 +*/ + { + "item_power_screen", + Pickup_Powerup, + Use_PowerArmor, + Drop_PowerArmor, + NULL, + "misc/ar3_pkup.wav", + "models/items/armor/screen/tris.md2", EF_ROTATE, + NULL, +/* icon */ "i_powerscreen", +/* pickup */ "Power Screen", +/* width */ 0, + 60, + NULL, + IT_ARMOR, + NULL, + 0, +/* precache */ "" + }, + +/*QUAKED item_power_shield (.3 .3 1) (-16 -16 -16) (16 16 16) 5 +*/ + { + "item_power_shield", + Pickup_Powerup, + Use_PowerArmor, + Drop_PowerArmor, + NULL, + "misc/ar3_pkup.wav", + "models/items/armor/shield/tris.md2", EF_ROTATE, + NULL, +/* icon */ "i_powershield", +/* pickup */ "Power Shield", +/* width */ 0, + 60, + NULL, + IT_ARMOR, + NULL, + 0, +/* precache */ "misc/power2.wav misc/power1.wav" + }, + + + // + // WEAPONS + // +/* weapon_grapple (.3 .3 1) (-16 -16 -16) (16 16 16) +always owned, never in the world +*/ + { + "weapon_grapple", + NULL, + Use_Weapon, + NULL, + CTFWeapon_Grapple, + "misc/w_pkup.wav", + NULL, 0, + "models/weapons/grapple/tris.md2", +/* icon */ "w_grapple", +/* pickup */ "Grapple", + 0, + 0, + NULL, + IT_WEAPON, +// 0, + NULL, + 0, +/* precache */ "weapons/grapple/grfire.wav weapons/grapple/grpull.wav weapons/grapple/grhang.wav weapons/grapple/grreset.wav weapons/grapple/grhit.wav" + }, +/* weapon_blaster (.3 .3 1) (-16 -16 -16) (16 16 16) 6 +always owned, never in the world +*/ + { + "weapon_blaster", + NULL, + Use_Weapon, + NULL, + Weapon_Blaster, + "misc/w_pkup.wav", + NULL, 0, + "models/weapons/v_blast/tris.md2", +/* icon */ "w_blaster", +/* pickup */ "Blaster", + 0, + 0, + NULL, + IT_WEAPON, +// WEAP_BLASTER, + NULL, + 0, +/* precache */ "weapons/blastf1a.wav misc/lasfly.wav" + }, + +/*QUAKED weapon_shotgun (.3 .3 1) (-16 -16 -16) (16 16 16) 7 +*/ + { + "weapon_shotgun", + Pickup_Weapon, + Use_Weapon, + Drop_Weapon, + Weapon_Shotgun, + "misc/w_pkup.wav", + "models/weapons/g_shotg/tris.md2", EF_ROTATE, + "models/weapons/v_shotg/tris.md2", +/* icon */ "w_shotgun", +/* pickup */ "Shotgun", + 0, + 1, + "Shells", + IT_WEAPON, +// WEAP_SHOTGUN, + NULL, + 0, +/* precache */ "weapons/shotgf1b.wav weapons/shotgr1b.wav" + }, + +/*QUAKED weapon_supershotgun (.3 .3 1) (-16 -16 -16) (16 16 16) 8 +*/ + { + "weapon_supershotgun", + Pickup_Weapon, + Use_Weapon, + Drop_Weapon, + Weapon_SuperShotgun, + "misc/w_pkup.wav", + "models/weapons/g_shotg2/tris.md2", EF_ROTATE, + "models/weapons/v_shotg2/tris.md2", +/* icon */ "w_sshotgun", +/* pickup */ "Super Shotgun", + 0, + 2, + "Shells", + IT_WEAPON, +// WEAP_SUPERSHOTGUN, + NULL, + 0, +/* precache */ "weapons/sshotf1b.wav" + }, + +/*QUAKED weapon_machinegun (.3 .3 1) (-16 -16 -16) (16 16 16) 9 +*/ + { + "weapon_machinegun", + Pickup_Weapon, + Use_Weapon, + Drop_Weapon, + Weapon_Machinegun, + "misc/w_pkup.wav", + "models/weapons/g_machn/tris.md2", EF_ROTATE, + "models/weapons/v_machn/tris.md2", +/* icon */ "w_machinegun", +/* pickup */ "Machinegun", + 0, + 1, + "Bullets", + IT_WEAPON, +// WEAP_MACHINEGUN, + NULL, + 0, +/* precache */ "weapons/machgf1b.wav weapons/machgf2b.wav weapons/machgf3b.wav weapons/machgf4b.wav weapons/machgf5b.wav" + }, + +/*QUAKED weapon_chaingun (.3 .3 1) (-16 -16 -16) (16 16 16) 10 +*/ + { + "weapon_chaingun", + Pickup_Weapon, + Use_Weapon, + Drop_Weapon, + Weapon_Chaingun, + "misc/w_pkup.wav", + "models/weapons/g_chain/tris.md2", EF_ROTATE, + "models/weapons/v_chain/tris.md2", +/* icon */ "w_chaingun", +/* pickup */ "Chaingun", + 0, + 1, + "Bullets", + IT_WEAPON, +// WEAP_CHAINGUN, + NULL, + 0, +/* precache */ "weapons/chngnu1a.wav weapons/chngnl1a.wav weapons/machgf3b.wav` weapons/chngnd1a.wav" + }, + +/*QUAKED ammo_trap (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "ammo_trap", + Pickup_Ammo, + Use_Weapon, + Drop_Ammo, + Weapon_Trap, + "misc/am_pkup.wav", + "models/weapons/g_trap/tris.md2", EF_ROTATE, + "models/weapons/v_trap/tris.md2", +/* icon */ "a_trap", +/* pickup */ "Trap", +/* width */ 3, + 1, + "trap", + IT_AMMO|IT_WEAPON, +// 0, + NULL, + AMMO_TRAP, +/* precache */ "weapons/trapcock.wav weapons/traploop.wav weapons/trapsuck.wav weapons/trapdown.wav" +// "weapons/hgrent1a.wav weapons/hgrena1b.wav weapons/hgrenc1b.wav weapons/hgrenb1a.wav weapons/hgrenb2a.wav " + }, + +/*QUAKED weapon_grenadelauncher (.3 .3 1) (-16 -16 -16) (16 16 16) 11 +*/ + { + "weapon_grenadelauncher", + Pickup_Weapon, + Use_Weapon, + Drop_Weapon, + Weapon_GrenadeLauncher, + "misc/w_pkup.wav", + "models/weapons/g_launch/tris.md2", EF_ROTATE, + "models/weapons/v_launch/tris.md2", +/* icon */ "w_glauncher", +/* pickup */ "Grenade Launcher", + 0, + 1, + "Grenades", + IT_WEAPON, + NULL, + 0, +/* precache */ "models/objects/grenade/tris.md2 weapons/grenlf1a.wav weapons/grenlr1b.wav weapons/grenlb1b.wav" + }, + +/*QUAKED weapon_rocketlauncher (.3 .3 1) (-16 -16 -16) (16 16 16) 12 +*/ + { + "weapon_rocketlauncher", + Pickup_Weapon, + Use_Weapon, + Drop_Weapon, + Weapon_RocketLauncher, + "misc/w_pkup.wav", + "models/weapons/g_rocket/tris.md2", EF_ROTATE, + "models/weapons/v_rocket/tris.md2", +/* icon */ "w_rlauncher", +/* pickup */ "Rocket Launcher", + 0, + 1, + "Rockets", + IT_WEAPON, + NULL, + 0, +/* precache */ "models/objects/rocket/tris.md2 weapons/rockfly.wav weapons/rocklf1a.wav weapons/rocklr1b.wav models/objects/debris2/tris.md2" + }, + +/*QUAKED weapon_hyperblaster (.3 .3 1) (-16 -16 -16) (16 16 16) 13 +*/ + { + "weapon_hyperblaster", + Pickup_Weapon, + // RAFAEL + Use_Weapon2, +/* Use_Weapon,*/ + Drop_Weapon, + Weapon_HyperBlaster, + "misc/w_pkup.wav", + "models/weapons/g_hyperb/tris.md2", EF_ROTATE, + "models/weapons/v_hyperb/tris.md2", +/* icon */ "w_hyperblaster", +/* pickup */ "HyperBlaster", + 0, + 1, + "Cells", + IT_WEAPON, + NULL, + 0, +/* precache */ "weapons/hyprbu1a.wav weapons/hyprbl1a.wav weapons/hyprbf1a.wav weapons/hyprbd1a.wav misc/lasfly.wav" + }, + + +/*QUAKED weapon_boomer (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + + { + "weapon_boomer", + Pickup_Weapon, + Use_Weapon, + Drop_Weapon, + Weapon_Ionripper, + "misc/w_pkup.wav", + "models/weapons/g_boom/tris.md2", EF_ROTATE, + "models/weapons/v_boomer/tris.md2", +/* icon */ "w_ripper", +/* pickup */ "Ionripper", + 0, + 2, + "Cells", + IT_WEAPON, +// WEAP_BOOMER, + NULL, + 0, +/* precache */ "weapons/rg_hum.wav weapons/rippfire.wav" + }, +// END 14-APR-98 + +/*QUAKED weapon_railgun (.3 .3 1) (-16 -16 -16) (16 16 16) 14 +*/ + { + "weapon_railgun", + Pickup_Weapon, + // RAFAEL + Use_Weapon2, +/* Use_Weapon,*/ + Drop_Weapon, + Weapon_Railgun, + "misc/w_pkup.wav", + "models/weapons/g_rail/tris.md2", EF_ROTATE, + "models/weapons/v_rail/tris.md2", +/* icon */ "w_railgun", +/* pickup */ "Railgun", + 0, + 1, + "Slugs", + IT_WEAPON, + NULL, + 0, +/* precache */ "weapons/rg_hum.wav" + }, + +// RAFAEL 14-APR-98 +/*QUAKED weapon_phalanx (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + + { + "weapon_phalanx", + Pickup_Weapon, + Use_Weapon, + Drop_Weapon, + Weapon_Phalanx, + "misc/w_pkup.wav", + "models/weapons/g_shotx/tris.md2", EF_ROTATE, + "models/weapons/v_shotx/tris.md2", +/* icon */ "w_phallanx", +/* pickup */ "Phalanx", + 0, + 1, + "Mag Slug", + IT_WEAPON, +// WEAP_PHALANX, + NULL, + 0, +/* precache */ "weapons/plasshot.wav" + }, + +/*QUAKED weapon_bfg (.3 .3 1) (-16 -16 -16) (16 16 16) 15 +*/ + { + "weapon_bfg", + Pickup_Weapon, + Use_Weapon, + Drop_Weapon, + Weapon_BFG, + "misc/w_pkup.wav", + "models/weapons/g_bfg/tris.md2", EF_ROTATE, + "models/weapons/v_bfg/tris.md2", +/* icon */ "w_bfg", +/* pickup */ "BFG10K", + 0, + 50, + "Cells", + IT_WEAPON, + + NULL, + 0, +/* precache */ "sprites/s_bfg1.sp2 sprites/s_bfg2.sp2 sprites/s_bfg3.sp2 weapons/bfg__f1y.wav weapons/bfg__l1a.wav weapons/bfg__x1b.wav weapons/bfg_hum.wav" + }, + +#if 0 +//ZOID +/*QUAKED weapon_laser (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "weapon_laser", + Pickup_Weapon, + Use_Weapon, + Drop_Weapon, + Weapon_Laser, + "misc/w_pkup.wav", + "models/weapons/g_laser/tris.md2", EF_ROTATE, + "models/weapons/v_laser/tris.md2", +/* icon */ "w_bfg", +/* pickup */ "Flashlight Laser", + 0, + 1, + "Cells", + IT_WEAPON, + NULL, + 0, +/* precache */ "" + }, +#endif + + // + // AMMO ITEMS + // + +/*QUAKED ammo_shells (.3 .3 1) (-16 -16 -16) (16 16 16) 16 +*/ + { + "ammo_shells", + Pickup_Ammo, + NULL, + Drop_Ammo, + NULL, + "misc/am_pkup.wav", + "models/items/ammo/shells/medium/tris.md2", 0, + NULL, +/* icon */ "a_shells", +/* pickup */ "Shells", +/* width */ 3, + 10, + NULL, + IT_AMMO, + NULL, + AMMO_SHELLS, +/* precache */ "" + }, + +/*QUAKED ammo_bullets (.3 .3 1) (-16 -16 -16) (16 16 16) 17 +*/ + { + "ammo_bullets", + Pickup_Ammo, + NULL, + Drop_Ammo, + NULL, + "misc/am_pkup.wav", + "models/items/ammo/bullets/medium/tris.md2", 0, + NULL, +/* icon */ "a_bullets", +/* pickup */ "Bullets", +/* width */ 3, + 50, + NULL, + IT_AMMO, + NULL, + AMMO_BULLETS, +/* precache */ "" + }, + +/*QUAKED ammo_cells (.3 .3 1) (-16 -16 -16) (16 16 16) 18 +*/ + { + "ammo_cells", + Pickup_Ammo, + NULL, + Drop_Ammo, + NULL, + "misc/am_pkup.wav", + "models/items/ammo/cells/medium/tris.md2", 0, + NULL, +/* icon */ "a_cells", +/* pickup */ "Cells", +/* width */ 3, + 50, + NULL, + IT_AMMO, + NULL, + AMMO_CELLS, +/* precache */ "" + }, + +/*QUAKED ammo_grenades (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "ammo_grenades", + Pickup_Ammo, + Use_Weapon, + Drop_Ammo, + Weapon_Grenade, + "misc/am_pkup.wav", + "models/items/ammo/grenades/medium/tris.md2", 0, + "models/weapons/v_handgr/tris.md2", +/* icon */ "a_grenades", +/* pickup */ "Grenades", +/* width */ 3, + 5, + "grenades", + IT_AMMO|IT_WEAPON, + NULL, + AMMO_GRENADES, +/* precache */ "weapons/hgrent1a.wav weapons/hgrena1b.wav weapons/hgrenc1b.wav weapons/hgrenb1a.wav weapons/hgrenb2a.wav " + }, + +/*QUAKED ammo_rockets (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "ammo_rockets", + Pickup_Ammo, + NULL, + Drop_Ammo, + NULL, + "misc/am_pkup.wav", + "models/items/ammo/rockets/medium/tris.md2", 0, + NULL, +/* icon */ "a_rockets", +/* pickup */ "Rockets", +/* width */ 3, + 5, + NULL, + IT_AMMO, + NULL, + AMMO_ROCKETS, +/* precache */ "" + }, + +/*QUAKED ammo_slugs (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "ammo_slugs", + Pickup_Ammo, + NULL, + Drop_Ammo, + NULL, + "misc/am_pkup.wav", + "models/items/ammo/slugs/medium/tris.md2", 0, + NULL, +/* icon */ "a_slugs", +/* pickup */ "Slugs", +/* width */ 3, + 10, + NULL, + IT_AMMO, + NULL, + AMMO_SLUGS, +/* precache */ "" + }, + +/*QUAKED ammo_magslug (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "ammo_magslug", + Pickup_Ammo, + NULL, + Drop_Ammo, + NULL, + "misc/am_pkup.wav", + "models/objects/ammo/tris.md2", 0, + NULL, +/* icon */ "a_mslugs", +/* pickup */ "Mag Slug", +/* width */ 3, + 10, + NULL, + IT_AMMO, +// 0, + NULL, + AMMO_MAGSLUG, +/* precache */ "" + }, + + // + // POWERUP ITEMS + // +/*QUAKED item_quad (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "item_quad", + Pickup_Powerup, + Use_Quad, + Drop_General, + NULL, + "items/pkup.wav", + "models/items/quaddama/tris.md2", EF_ROTATE, + NULL, +/* icon */ "p_quad", +/* pickup */ "Quad Damage", +/* width */ 2, + 60, + NULL, + 0, + NULL, + 0, +/* precache */ "items/damage.wav items/damage2.wav items/damage3.wav" + }, + +/*QUAKED item_quadfire (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "item_quadfire", + Pickup_Powerup, + Use_QuadFire, + Drop_General, + NULL, + "items/pkup.wav", + "models/items/quadfire/tris.md2", EF_ROTATE, + NULL, +/* icon */ "p_quadfire", + +/* pickup */ "DualFire Damage", +/* width */ 2, + 60, + NULL, +// IT_POWERUP, + 0, + NULL, + 0, +/* precache */ "items/quadfire1.wav items/quadfire2.wav items/quadfire3.wav" + }, + +/*QUAKED item_invulnerability (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "item_invulnerability", + Pickup_Powerup, + Use_Invulnerability, + Drop_General, + NULL, + "items/pkup.wav", + "models/items/invulner/tris.md2", EF_ROTATE, + NULL, +/* icon */ "p_invulnerability", +/* pickup */ "Invulnerability", +/* width */ 2, + 300, + NULL, + 0, + NULL, + 0, +/* precache */ "items/protect.wav items/protect2.wav items/protect3.wav" + }, + +/*QUAKED item_silencer (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "item_silencer", + Pickup_Powerup, + Use_Silencer, + Drop_General, + NULL, + "items/pkup.wav", + "models/items/silencer/tris.md2", EF_ROTATE, + NULL, +/* icon */ "p_silencer", +/* pickup */ "Silencer", +/* width */ 2, + 60, + NULL, + 0, + NULL, + 0, +/* precache */ "" + }, + +/*QUAKED item_breather (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "item_breather", + Pickup_Powerup, + Use_Breather, + Drop_General, + NULL, + "items/pkup.wav", + "models/items/breather/tris.md2", EF_ROTATE, + NULL, +/* icon */ "p_rebreather", +/* pickup */ "Rebreather", +/* width */ 2, + 60, + NULL, + 0, + NULL, + 0, +/* precache */ "items/airout.wav" + }, + +/*QUAKED item_enviro (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "item_enviro", + Pickup_Powerup, + Use_Envirosuit, + Drop_General, + NULL, + "items/pkup.wav", + "models/items/enviro/tris.md2", EF_ROTATE, + NULL, +/* icon */ "p_envirosuit", +/* pickup */ "Environment Suit", +/* width */ 2, + 60, + NULL, + 0, + NULL, + 0, +/* precache */ "items/airout.wav" + }, + +/*QUAKED item_ancient_head (.3 .3 1) (-16 -16 -16) (16 16 16) +Special item that gives +2 to maximum health +*/ + { + "item_ancient_head", + Pickup_AncientHead, + NULL, + NULL, + NULL, + "items/pkup.wav", + "models/items/c_head/tris.md2", EF_ROTATE, + NULL, +/* icon */ "i_fixme", +/* pickup */ "Ancient Head", +/* width */ 2, + 60, + NULL, + 0, + NULL, + 0, +/* precache */ "" + }, + +/*QUAKED item_adrenaline (.3 .3 1) (-16 -16 -16) (16 16 16) +gives +1 to maximum health +*/ + { + "item_adrenaline", + Pickup_Adrenaline, + NULL, + NULL, + NULL, + "items/pkup.wav", + "models/items/adrenal/tris.md2", EF_ROTATE, + NULL, +/* icon */ "p_adrenaline", +/* pickup */ "Adrenaline", +/* width */ 2, + 60, + NULL, + 0, + NULL, + 0, +/* precache */ "" + }, + +/*QUAKED item_bandolier (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "item_bandolier", + Pickup_Bandolier, + NULL, + NULL, + NULL, + "items/pkup.wav", + "models/items/band/tris.md2", EF_ROTATE, + NULL, +/* icon */ "p_bandolier", +/* pickup */ "Bandolier", +/* width */ 2, + 60, + NULL, + 0, + NULL, + 0, +/* precache */ "" + }, + +/*QUAKED item_pack (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "item_pack", + Pickup_Pack, + NULL, + NULL, + NULL, + "items/pkup.wav", + "models/items/pack/tris.md2", EF_ROTATE, + NULL, +/* icon */ "i_pack", +/* pickup */ "Ammo Pack", +/* width */ 2, + 180, + NULL, + 0, + NULL, + 0, +/* precache */ "" + }, +#if 0 + // + // KEYS + // +/*QUAKED key_data_cd (0 .5 .8) (-16 -16 -16) (16 16 16) +key for computer centers +*/ + { + "key_data_cd", + Pickup_Key, + NULL, + Drop_General, + NULL, + "items/pkup.wav", + "models/items/keys/data_cd/tris.md2", EF_ROTATE, + NULL, + "k_datacd", + "Data CD", + 2, + 0, + NULL, + 0, + NULL, + 0, +/* precache */ "" + }, + +/*QUAKED key_power_cube (0 .5 .8) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN NO_TOUCH +warehouse circuits +*/ + { + "key_power_cube", + Pickup_Key, + NULL, + Drop_General, + NULL, + "items/pkup.wav", + "models/items/keys/power/tris.md2", EF_ROTATE, + NULL, + "k_powercube", + "Power Cube", + 2, + 0, + NULL, + 0, + NULL, + 0, +/* precache */ "" + }, + +/*QUAKED key_pyramid (0 .5 .8) (-16 -16 -16) (16 16 16) +key for the entrance of jail3 +*/ + { + "key_pyramid", + Pickup_Key, + NULL, + Drop_General, + NULL, + "items/pkup.wav", + "models/items/keys/pyramid/tris.md2", EF_ROTATE, + NULL, + "k_pyramid", + "Pyramid Key", + 2, + 0, + NULL, + 0, + NULL, + 0, +/* precache */ "" + }, + +/*QUAKED key_data_spinner (0 .5 .8) (-16 -16 -16) (16 16 16) +key for the city computer +*/ + { + "key_data_spinner", + Pickup_Key, + NULL, + Drop_General, + NULL, + "items/pkup.wav", + "models/items/keys/spinner/tris.md2", EF_ROTATE, + NULL, + "k_dataspin", + "Data Spinner", + 2, + 0, + NULL, + 0, + NULL, + 0, +/* precache */ "" + }, + +/*QUAKED key_pass (0 .5 .8) (-16 -16 -16) (16 16 16) +security pass for the security level +*/ + { + "key_pass", + Pickup_Key, + NULL, + Drop_General, + NULL, + "items/pkup.wav", + "models/items/keys/pass/tris.md2", EF_ROTATE, + NULL, + "k_security", + "Security Pass", + 2, + 0, + NULL, + 0, + NULL, + 0, +/* precache */ "" + }, + +/*QUAKED key_blue_key (0 .5 .8) (-16 -16 -16) (16 16 16) +normal door key - blue +*/ + { + "key_blue_key", + Pickup_Key, + NULL, + Drop_General, + NULL, + "items/pkup.wav", + "models/items/keys/key/tris.md2", EF_ROTATE, + NULL, + "k_bluekey", + "Blue Key", + 2, + 0, + NULL, + 0, + NULL, + 0, +/* precache */ "" + }, + +/*QUAKED key_red_key (0 .5 .8) (-16 -16 -16) (16 16 16) +normal door key - red +*/ + { + "key_red_key", + Pickup_Key, + NULL, + Drop_General, + NULL, + "items/pkup.wav", + "models/items/keys/red_key/tris.md2", EF_ROTATE, + NULL, + "k_redkey", + "Red Key", + 2, + 0, + NULL, + 0, + NULL, + 0, +/* precache */ "" + }, + +// RAFAEL +/*QUAKED key_green_key (0 .5 .8) (-16 -16 -16) (16 16 16) +normal door key - blue +*/ + { + "key_green_key", + Pickup_Key, + NULL, + Drop_General, + NULL, + "items/pkup.wav", + "models/items/keys/green_key/tris.md2", EF_ROTATE, + NULL, + "k_green", + "Green Key", + 2, + 0, + NULL, + IT_STAY_COOP|IT_KEY, +// 0, + NULL, + 0, +/* precache */ "" + }, +/*QUAKED key_commander_head (0 .5 .8) (-16 -16 -16) (16 16 16) +tank commander's head +*/ + { + "key_commander_head", + Pickup_Key, + NULL, + Drop_General, + NULL, + "items/pkup.wav", + "models/monsters/commandr/head/tris.md2", EF_GIB, + NULL, +/* icon */ "k_comhead", +/* pickup */ "Commander's Head", +/* width */ 2, + 0, + NULL, + 0, + NULL, + 0, +/* precache */ "" + }, + +/*QUAKED key_airstrike_target (0 .5 .8) (-16 -16 -16) (16 16 16) +tank commander's head +*/ + { + "key_airstrike_target", + Pickup_Key, + NULL, + Drop_General, + NULL, + "items/pkup.wav", + "models/items/keys/target/tris.md2", EF_ROTATE, + NULL, +/* icon */ "i_airstrike", +/* pickup */ "Airstrike Marker", +/* width */ 2, + 0, + NULL, + 0, + NULL, + 0, +/* precache */ "" + }, +#endif + { + NULL, + Pickup_Health, + NULL, + NULL, + NULL, + "items/pkup.wav", + NULL, 0, + NULL, +/* icon */ "i_health", +/* pickup */ "Health", +/* width */ 3, + 0, + NULL, + 0, + NULL, + 0, +/* precache */ "" + }, + +//ZOID +/*QUAKED item_flag_team1 (1 0.2 0) (-16 -16 -24) (16 16 32) +*/ + { + "item_flag_team1", + CTFPickup_Flag, + NULL, + CTFDrop_Flag, //Should this be null if we don't want players to drop it manually? + NULL, + "ctf/flagtk.wav", + "players/male/flag1.md2", EF_FLAG1, + NULL, +/* icon */ "i_ctf1", +/* pickup */ "Red Flag", +/* width */ 2, + 0, + NULL, + 0, + NULL, + 0, +/* precache */ "ctf/flagcap.wav" + }, + +/*QUAKED item_flag_team2 (1 0.2 0) (-16 -16 -24) (16 16 32) +*/ + { + "item_flag_team2", + CTFPickup_Flag, + NULL, + CTFDrop_Flag, //Should this be null if we don't want players to drop it manually? + NULL, + "ctf/flagtk.wav", + "players/male/flag2.md2", EF_FLAG2, + NULL, +/* icon */ "i_ctf2", +/* pickup */ "Blue Flag", +/* width */ 2, + 0, + NULL, + 0, + NULL, + 0, +/* precache */ "ctf/flagcap.wav" + }, + +/*QUAKED item_flag_zig (1 0.2 0) (-16 -16 -24) (16 16 32) +*/ + { + "item_flag_zig", + ZIGPickup_Flag, + NULL, + ZIGDrop_Flag, //Should this be null if we don't want players to drop it manually? + NULL, + "3zb/emgcall.wav", + "models/zflag.md2", EF_FLAG2, + NULL, +/* icon */ "i_zig", +/* pickup */ "Zig Flag", +/* width */ 2, + 0, + NULL, + 0, + NULL, + 0, +/* precache */ "ctf/flagcap.wav" + }, +/* Resistance Tech */ + { + "item_tech1", + CTFPickup_Tech, + NULL, + CTFDrop_Tech, //Should this be null if we don't want players to drop it manually? + NULL, + "items/pkup.wav", + "models/ctf/resistance/tris.md2", EF_ROTATE, + NULL, +/* icon */ "tech1", +/* pickup */ "Disruptor Shield", +/* width */ 2, + 0, + NULL, + IT_TECH, + NULL, + 0, +/* precache */ "ctf/tech1.wav" + }, + +/* Strength Tech */ + { + "item_tech2", + CTFPickup_Tech, + NULL, + CTFDrop_Tech, //Should this be null if we don't want players to drop it manually? + NULL, + "items/pkup.wav", + "models/ctf/strength/tris.md2", EF_ROTATE, + NULL, +/* icon */ "tech2", +/* pickup */ "Power Amplifier", +/* width */ 2, + 0, + NULL, + IT_TECH, + NULL, + 0, +/* precache */ "ctf/tech2.wav ctf/tech2x.wav" + }, + +/* Haste Tech */ + { + "item_tech3", + CTFPickup_Tech, + NULL, + CTFDrop_Tech, //Should this be null if we don't want players to drop it manually? + NULL, + "items/pkup.wav", + "models/ctf/haste/tris.md2", EF_ROTATE, + NULL, +/* icon */ "tech3", +/* pickup */ "Time Accel", +/* width */ 2, + 0, + NULL, + IT_TECH, + NULL, + 0, +/* precache */ "ctf/tech3.wav" + }, + +/* Regeneration Tech */ + { + "item_tech4", + CTFPickup_Tech, + NULL, + CTFDrop_Tech, //Should this be null if we don't want players to drop it manually? + NULL, + "items/pkup.wav", + "models/ctf/regeneration/tris.md2", EF_ROTATE, + NULL, +/* icon */ "tech4", +/* pickup */ "AutoDoc", +/* width */ 2, + 0, + NULL, + IT_TECH, + NULL, + 0, +/* precache */ "ctf/tech4.wav" + }, + +//ZOID + + // end of list marker + {NULL} +}; + + +/*QUAKED item_health (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ +void SP_item_health (edict_t *self) +{ + if ( deathmatch->value && ((int)dmflags->value & DF_NO_HEALTH) ) + { + G_FreeEdict (self); + return; + } + + self->model = "models/items/healing/medium/tris.md2"; + self->count = 10; + SpawnItem (self, FindItem ("Health")); + gi.soundindex ("items/n_health.wav"); +} + +/*QUAKED item_health_small (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ +void SP_item_health_small (edict_t *self) +{ + if ( deathmatch->value && ((int)dmflags->value & DF_NO_HEALTH) ) + { + G_FreeEdict (self); + return; + } + + self->model = "models/items/healing/stimpack/tris.md2"; + self->count = 2; + SpawnItem (self, FindItem ("Health")); + self->style = HEALTH_IGNORE_MAX; + gi.soundindex ("items/s_health.wav"); +} + +/*QUAKED item_health_large (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ +void SP_item_health_large (edict_t *self) +{ + if ( deathmatch->value && ((int)dmflags->value & DF_NO_HEALTH) ) + { + G_FreeEdict (self); + return; + } + + self->model = "models/items/healing/large/tris.md2"; + self->count = 25; + SpawnItem (self, FindItem ("Health")); + gi.soundindex ("items/l_health.wav"); +} + +/*QUAKED item_health_mega (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ +void SP_item_health_mega (edict_t *self) +{ + if ( deathmatch->value && ((int)dmflags->value & DF_NO_HEALTH) ) + { + G_FreeEdict (self); + return; + } + + self->model = "models/items/mega_h/tris.md2"; + self->count = 100; + SpawnItem (self, FindItem ("Health")); + gi.soundindex ("items/m_health.wav"); + self->style = HEALTH_IGNORE_MAX|HEALTH_TIMED; +} + +// RAFAEL +void SP_item_foodcube (edict_t *self) +{ + if ( deathmatch->value && ((int)dmflags->value & DF_NO_HEALTH) ) + { + G_FreeEdict (self); + return; + } + + self->model = "models/objects/trapfx/tris.md2"; + SpawnItem (self, FindItem ("Health")); + self->spawnflags |= DROPPED_ITEM; + self->style = HEALTH_IGNORE_MAX; + gi.soundindex ("items/s_health.wav"); + self->classname = "foodcube"; +} + + +void InitItems (void) +{ + game.num_items = sizeof(itemlist)/sizeof(itemlist[0]) - 1; +} + + + +/* +=============== +SetItemNames + +Called by worldspawn +=============== +*/ +void SetItemNames (void) +{ + int i; + gitem_t *it; + + for (i=0 ; ipickup_name); + } + + jacket_armor_index = ITEM_INDEX(FindItem("Jacket Armor")); + combat_armor_index = ITEM_INDEX(FindItem("Combat Armor")); + body_armor_index = ITEM_INDEX(FindItem("Body Armor")); + power_screen_index = ITEM_INDEX(FindItem("Power Screen")); + power_shield_index = ITEM_INDEX(FindItem("Power Shield")); +} diff --git a/src/g_local.h b/src/g_local.h new file mode 100644 index 0000000..94b99d8 --- /dev/null +++ b/src/g_local.h @@ -0,0 +1,1257 @@ +// g_local.h -- local definitions for game module + +#include "q_shared.h" + +#ifndef G_LOCAL +#define G_LOCAL + +// define GAME_INCLUDE so that game.h does not define the +// short, server-visible gclient_t and edict_t structures, +// because we define the full size ones in this file +#define GAME_INCLUDE +#include "game.h" + +//ZOID +#include "p_menu.h" +//ZOID + +// the "gameversion" client command will print this plus compile date +#define GAMEVERSION "baseq2" + +// protocol bytes that can be directly added to messages +#define svc_muzzleflash 1 +#define svc_muzzleflash2 2 +#define svc_temp_entity 3 +#define svc_layout 4 +#define svc_inventory 5 +#define svc_stufftext 11 +//================================================================== + +// view pitching times +#define DAMAGE_TIME 0.5 +#define FALL_TIME 0.3 + + +// edict->spawnflags +// these are set with checkboxes on each entity in the map editor +#define SPAWNFLAG_NOT_EASY 0x00000100 +#define SPAWNFLAG_NOT_MEDIUM 0x00000200 +#define SPAWNFLAG_NOT_HARD 0x00000400 +#define SPAWNFLAG_NOT_DEATHMATCH 0x00000800 +#define SPAWNFLAG_NOT_COOP 0x00001000 + +// edict->flags +#define FL_FLY 0x00000001 +#define FL_SWIM 0x00000002 // implied immunity to drowining +#define FL_IMMUNE_LASER 0x00000004 +#define FL_INWATER 0x00000008 +#define FL_GODMODE 0x00000010 +#define FL_NOTARGET 0x00000020 +#define FL_IMMUNE_SLIME 0x00000040 +#define FL_IMMUNE_LAVA 0x00000080 +#define FL_PARTIALGROUND 0x00000100 // not all corners are valid +#define FL_WATERJUMP 0x00000200 // player jumping out of water +#define FL_TEAMSLAVE 0x00000400 // not the first on the team +#define FL_NO_KNOCKBACK 0x00000800 +#define FL_POWER_ARMOR 0x00001000 // power armor (if any) is active +#define FL_RESPAWN 0x80000000 // used for item respawning + + +#define FRAMETIME 0.1 + +// memory tags to allow dynamic memory to be cleaned up +#define TAG_GAME 765 // clear when unloading the dll +#define TAG_LEVEL 766 // clear when loading a new level + + +#define MELEE_DISTANCE 80 + +#define BODY_QUEUE_SIZE 8 + +typedef enum +{ + DAMAGE_NO, + DAMAGE_YES, // will take damage if hit + DAMAGE_AIM // auto targeting recognizes this +} damage_t; + +typedef enum +{ + WEAPON_READY, + WEAPON_ACTIVATING, + WEAPON_DROPPING, + WEAPON_FIRING +} weaponstate_t; + +typedef enum +{ + AMMO_BULLETS, + AMMO_SHELLS, + AMMO_ROCKETS, + AMMO_GRENADES, + AMMO_CELLS, + AMMO_SLUGS, + // RAFAEL + AMMO_MAGSLUG, + AMMO_TRAP +} ammo_t; + + +//deadflag +#define DEAD_NO 0 +#define DEAD_DYING 1 +#define DEAD_DEAD 2 +#define DEAD_RESPAWNABLE 3 + +//range +#define RANGE_MELEE 0 +#define RANGE_NEAR 1 +#define RANGE_MID 2 +#define RANGE_FAR 3 + +//gib types +#define GIB_ORGANIC 0 +#define GIB_METALLIC 1 + +//monster ai flags +#define AI_STAND_GROUND 0x00000001 +#define AI_TEMP_STAND_GROUND 0x00000002 +#define AI_SOUND_TARGET 0x00000004 +#define AI_LOST_SIGHT 0x00000008 +#define AI_PURSUIT_LAST_SEEN 0x00000010 +#define AI_PURSUE_NEXT 0x00000020 +#define AI_PURSUE_TEMP 0x00000040 +#define AI_HOLD_FRAME 0x00000080 +#define AI_GOOD_GUY 0x00000100 +#define AI_BRUTAL 0x00000200 +#define AI_NOSTEP 0x00000400 +#define AI_DUCKED 0x00000800 +#define AI_COMBAT_POINT 0x00001000 +#define AI_MEDIC 0x00002000 +#define AI_RESURRECTING 0x00004000 + +//monster attack state +#define AS_STRAIGHT 1 +#define AS_SLIDING 2 +#define AS_MELEE 3 +#define AS_MISSILE 4 + +// armor types +#define ARMOR_NONE 0 +#define ARMOR_JACKET 1 +#define ARMOR_COMBAT 2 +#define ARMOR_BODY 3 +#define ARMOR_SHARD 4 + +// power armor types +#define POWER_ARMOR_NONE 0 +#define POWER_ARMOR_SCREEN 1 +#define POWER_ARMOR_SHIELD 2 + +// handedness values +#define RIGHT_HANDED 0 +#define LEFT_HANDED 1 +#define CENTER_HANDED 2 + + +// game.serverflags values +#define SFL_CROSS_TRIGGER_1 0x00000001 +#define SFL_CROSS_TRIGGER_2 0x00000002 +#define SFL_CROSS_TRIGGER_3 0x00000004 +#define SFL_CROSS_TRIGGER_4 0x00000008 +#define SFL_CROSS_TRIGGER_5 0x00000010 +#define SFL_CROSS_TRIGGER_6 0x00000020 +#define SFL_CROSS_TRIGGER_7 0x00000040 +#define SFL_CROSS_TRIGGER_8 0x00000080 +#define SFL_CROSS_TRIGGER_MASK 0x000000ff + + +// noise types for PlayerNoise +#define PNOISE_SELF 0 +#define PNOISE_WEAPON 1 +#define PNOISE_IMPACT 2 + + +//3ZB CTF state + +#define GETTER 0 +#define DEFENDER 1 +#define SUPPORTER 2 +#define CARRIER 3 + +// edict->movetype values +typedef enum +{ +MOVETYPE_NONE, // never moves +MOVETYPE_NOCLIP, // origin and angles change with no interaction +MOVETYPE_PUSH, // no clip to world, push on box contact +MOVETYPE_STOP, // no clip to world, stops on box contact + +MOVETYPE_WALK, // gravity +MOVETYPE_STEP, // gravity, special edge handling +MOVETYPE_FLY, +MOVETYPE_TOSS, // gravity +MOVETYPE_FLYMISSILE, // extra size to monsters +MOVETYPE_BOUNCE, +// RAFAEL +MOVETYPE_WALLBOUNCE +} movetype_t; + + + +typedef struct +{ + int base_count; + int max_count; + float normal_protection; + float energy_protection; + int armor; +} gitem_armor_t; + + +// gitem_t->flags +#define IT_WEAPON 1 // use makes active weapon +#define IT_AMMO 2 +#define IT_ARMOR 4 +#define IT_STAY_COOP 8 +#define IT_KEY 16 +#define IT_POWERUP 32 +//ZOID +#define IT_TECH 64 +//ZOID + +// gitem_t->weapmodel for weapons indicates model index +#define WEAP_BLASTER 1 +#define WEAP_SHOTGUN 2 +#define WEAP_SUPERSHOTGUN 3 +#define WEAP_MACHINEGUN 4 +#define WEAP_CHAINGUN 5 +#define WEAP_GRENADES 6 +#define WEAP_GRENADELAUNCHER 7 +#define WEAP_ROCKETLAUNCHER 8 +#define WEAP_HYPERBLASTER 9 +#define WEAP_RAILGUN 10 +#define WEAP_BFG 11 +#define WEAP_PHALANX 12 +#define WEAP_BOOMER 13 + +#define WEAP_DISRUPTOR 12 // PGM +#define WEAP_ETFRIFLE 13 // PGM +#define WEAP_PLASMA 14 // PGM +#define WEAP_PROXLAUNCH 15 // PGM +#define WEAP_CHAINFIST 16 // PGM + +#define WEAP_TRAP 17 + +#define WEAP_GRAPPLE 20 + +#define MPI_QUAD 21 +#define MPI_PENTA 22 +#define MPI_QUADF 23 + +#define MPI_INDEX 24 //MPI count + +typedef struct gitem_s +{ + char *classname; // spawning name + qboolean (*pickup)(struct edict_s *ent, struct edict_s *other); + void (*use)(struct edict_s *ent, struct gitem_s *item); + void (*drop)(struct edict_s *ent, struct gitem_s *item); + void (*weaponthink)(struct edict_s *ent); + char *pickup_sound; + char *world_model; + int world_model_flags; + char *view_model; + + // client side info + char *icon; + char *pickup_name; // for printing on pickup + int count_width; // number of digits to display by icon + + int quantity; // for ammo how much, for weapons how much is used per shot + char *ammo; // for weapons + int flags; // IT_* flags + +// int weapmodel; // weapon model index (for weapons) + + void *info; + int tag; + + char *precaches; // string of all models, sounds, and images this item will use +} gitem_t; + + + +// +// this structure is left intact through an entire game +// it should be initialized at dll load time, and read/written to +// the server.ssv file for savegames +// +typedef struct +{ + char helpmessage1[512]; + char helpmessage2[512]; + int helpchanged; // flash F1 icon if non 0, play sound + // and increment only if 1, 2, or 3 + + gclient_t *clients; // [maxclients] + + // can't store spawnpoint in level, because + // it would get overwritten by the savegame restore + char spawnpoint[512]; // needed for coop respawns + + // store latched cvars here that we want to get at often + int maxclients; + int maxentities; + + // cross level triggers + int serverflags; + + // items + int num_items; + + qboolean autosaved; +} game_locals_t; + + +// +// this structure is cleared as each map is entered +// it is read/written to the level.sav file for savegames +// +typedef struct +{ + int framenum; + float time; + + char level_name[MAX_QPATH]; // the descriptive name (Outer Base, etc) + char mapname[MAX_QPATH]; // the server name (base1, etc) + char nextmap[MAX_QPATH]; // go here when fraglimit is hit + + // intermission state + float intermissiontime; // time the intermission was started + char *changemap; + int exitintermission; + vec3_t intermission_origin; + vec3_t intermission_angle; + + edict_t *sight_client; // changed once each frame for coop games + + edict_t *sight_entity; + int sight_entity_framenum; + edict_t *sound_entity; + int sound_entity_framenum; + edict_t *sound2_entity; + int sound2_entity_framenum; + + int pic_health; + + int total_secrets; + int found_secrets; + + int total_goals; + int found_goals; + + int total_monsters; + int killed_monsters; + + edict_t *current_entity; // entity running from G_RunFrame + int body_que; // dead bodies + + int power_cubes; // ugly necessity for coop +} level_locals_t; + + +// spawn_temp_t is only used to hold entity field values that +// can be set from the editor, but aren't actualy present +// in edict_t during gameplay +typedef struct +{ + // world vars + char *sky; + float skyrotate; + vec3_t skyaxis; + char *nextmap; + + int lip; + int distance; + int height; + char *noise; + float pausetime; + char *item; + char *gravity; + + float minyaw; + float maxyaw; + float minpitch; + float maxpitch; +} spawn_temp_t; + + +typedef struct +{ + // fixed data + vec3_t start_origin; + vec3_t start_angles; + vec3_t end_origin; //BFGのターゲットポイントに不正使用 + vec3_t end_angles; + + int sound_start; //スナイパーのアクティベートフラグ + int sound_middle; + int sound_end; //hokutoのクラス + + float accel; + float speed; //bot 落下時の移動量に不正使用 + float decel; //水面滞在時間に不正使用 + float distance; //スナイパー用FOV値 + + float wait; + + // state data + int state; //CTFステータスに不正使用 + vec3_t dir; + float current_speed; + float move_speed; + float next_speed; + float remaining_distance; + float decel_distance; + void (*endfunc)(edict_t *); +} moveinfo_t; + + +typedef struct +{ + void (*aifunc)(edict_t *self, float dist); + float dist; + void (*thinkfunc)(edict_t *self); +} mframe_t; + +typedef struct +{ + int firstframe; + int lastframe; + mframe_t *frame; + void (*endfunc)(edict_t *self); +} mmove_t; + +typedef struct +{ + mmove_t *currentmove; + int aiflags; + int nextframe; + float scale; + + void (*stand)(edict_t *self); + void (*idle)(edict_t *self); + void (*search)(edict_t *self); + void (*walk)(edict_t *self); + void (*run)(edict_t *self); + void (*dodge)(edict_t *self, edict_t *other, float eta); + void (*attack)(edict_t *self); + void (*melee)(edict_t *self); + void (*sight)(edict_t *self, edict_t *other); + qboolean (*checkattack)(edict_t *self); + + float pausetime; + float attack_finished; + + vec3_t saved_goal; + float search_time; + float trail_time; + vec3_t last_sighting; + int attack_state; + int lefty; + float idle_time; + int linkcount; + + int power_armor_type; + int power_armor_power; +} monsterinfo_t; + + + +extern game_locals_t game; +extern level_locals_t level; +extern game_import_t gi; +extern game_export_t globals; +extern spawn_temp_t st; + +extern int sm_meat_index; +extern int snd_fry; + +extern int jacket_armor_index; +extern int combat_armor_index; +extern int body_armor_index; + + +// means of death +#define MOD_UNKNOWN 0 +#define MOD_BLASTER 1 +#define MOD_SHOTGUN 2 +#define MOD_SSHOTGUN 3 +#define MOD_MACHINEGUN 4 +#define MOD_CHAINGUN 5 +#define MOD_GRENADE 6 +#define MOD_G_SPLASH 7 +#define MOD_ROCKET 8 +#define MOD_R_SPLASH 9 +#define MOD_HYPERBLASTER 10 +#define MOD_RAILGUN 11 +#define MOD_BFG_LASER 12 +#define MOD_BFG_BLAST 13 +#define MOD_BFG_EFFECT 14 +#define MOD_HANDGRENADE 15 +#define MOD_HG_SPLASH 16 +#define MOD_WATER 17 +#define MOD_SLIME 18 +#define MOD_LAVA 19 +#define MOD_CRUSH 20 +#define MOD_TELEFRAG 21 +#define MOD_FALLING 22 +#define MOD_SUICIDE 23 +#define MOD_HELD_GRENADE 24 +#define MOD_EXPLOSIVE 25 +#define MOD_BARREL 26 +#define MOD_BOMB 27 +#define MOD_EXIT 28 +#define MOD_SPLASH 29 +#define MOD_TARGET_LASER 30 +#define MOD_TRIGGER_HURT 31 +#define MOD_HIT 32 +#define MOD_TARGET_BLASTER 33 +#define MOD_GRAPPLE 34 +// RAFAEL 14-APR-98 +#define MOD_RIPPER 35 +#define MOD_PHALANX 36 +#define MOD_BRAINTENTACLE 37 +#define MOD_BLASTOFF 38 +#define MOD_GEKK 39 +#define MOD_TRAP 40 +// END 14-APR-98 +#define MOD_FRIENDLY_FIRE 0x8000000 + +extern int meansOfDeath; + + +extern edict_t *g_edicts; + +#define FOFS(x) (int)&(((edict_t *)0)->x) +#define STOFS(x) (int)&(((spawn_temp_t *)0)->x) +#define LLOFS(x) (int)&(((level_locals_t *)0)->x) +#define CLOFS(x) (int)&(((gclient_t *)0)->x) + +#define random() ((rand () & 0x7fff) / ((float)0x7fff)) +#define crandom() (2.0 * (random() - 0.5)) + +extern cvar_t *maxentities; +extern cvar_t *deathmatch; +extern cvar_t *coop; +extern cvar_t *dmflags; +extern cvar_t *skill; +extern cvar_t *fraglimit; +extern cvar_t *timelimit; +//ZOID +extern cvar_t *capturelimit; +//ZOID +extern cvar_t *password; +extern cvar_t *spectator_password; +extern cvar_t *g_select_empty; +extern cvar_t *dedicated; + +extern cvar_t *sv_gravity; +extern cvar_t *sv_maxvelocity; + +extern cvar_t *gun_x, *gun_y, *gun_z; +extern cvar_t *sv_rollspeed; +extern cvar_t *sv_rollangle; + +extern cvar_t *run_pitch; +extern cvar_t *run_roll; +extern cvar_t *bob_up; +extern cvar_t *bob_pitch; +extern cvar_t *bob_roll; + +extern cvar_t *sv_cheats; +extern cvar_t *maxclients; +extern cvar_t *maxspectators; + +extern cvar_t *filterban; + +//ponpoko +extern cvar_t *gamepath; +extern cvar_t *chedit; +extern cvar_t *vwep; +extern cvar_t *maplist; +extern cvar_t *botlist; +extern cvar_t *autospawn; +extern cvar_t *zigmode; +extern float spawncycle; +//ponpoko + +//ZOID +extern qboolean is_quad; +//ZOID + +#define world (&g_edicts[0]) + +// item spawnflags +#define ITEM_TRIGGER_SPAWN 0x00000001 +#define ITEM_NO_TOUCH 0x00000002 +// 6 bits reserved for editor flags +// 8 bits used as power cube id bits for coop games +#define DROPPED_ITEM 0x00010000 +#define DROPPED_PLAYER_ITEM 0x00020000 +#define ITEM_TARGETS_USED 0x00040000 + +// +// fields are needed for spawning from the entity string +// and saving / loading games +// +#define FFL_SPAWNTEMP 1 +#define FFL_NOSPAWN 2 + +typedef enum { + F_INT, + F_FLOAT, + F_LSTRING, // string on disk, pointer in memory, TAG_LEVEL + F_GSTRING, // string on disk, pointer in memory, TAG_GAME + F_VECTOR, + F_ANGLEHACK, + F_EDICT, // index on disk, pointer in memory + F_ITEM, // index on disk, pointer in memory + F_CLIENT, // index on disk, pointer in memory + F_FUNCTION, + F_MMOVE, + F_IGNORE +} fieldtype_t; + +typedef struct +{ + char *name; + int ofs; + fieldtype_t type; + int flags; +} field_t; + + +extern field_t fields[]; +extern gitem_t itemlist[]; + + +// +// g_cmds.c +// +void Cmd_Help_f (edict_t *ent); +void Cmd_Score_f (edict_t *ent); + +// +// g_items.c +// +void PrecacheItem (gitem_t *it); +void InitItems (void); +void SetItemNames (void); +gitem_t *FindItem (char *pickup_name); +gitem_t *FindItemByClassname (char *classname); +#define ITEM_INDEX(x) ((x)-itemlist) +edict_t *Drop_Item (edict_t *ent, gitem_t *item); +void SetRespawn (edict_t *ent, float delay); +void ChangeWeapon (edict_t *ent); +void SpawnItem (edict_t *ent, gitem_t *item); +void Think_Weapon (edict_t *ent); +int ArmorIndex (edict_t *ent); +int PowerArmorType (edict_t *ent); +gitem_t *GetItemByIndex (int index); +qboolean Add_Ammo (edict_t *ent, gitem_t *item, int count); +void Touch_Item (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf); + +// +// g_utils.c +// +qboolean KillBox (edict_t *ent); +void G_ProjectSource (vec3_t point, vec3_t distance, vec3_t forward, vec3_t right, vec3_t result); +edict_t *G_Find (edict_t *from, int fieldofs, char *match); +edict_t *findradius (edict_t *from, vec3_t org, float rad); +edict_t *G_PickTarget (char *targetname); +void G_UseTargets (edict_t *ent, edict_t *activator); +void G_SetMovedir (vec3_t angles, vec3_t movedir); + +void G_InitEdict (edict_t *e); +edict_t *G_Spawn (void); +void G_FreeEdict (edict_t *e); + +void G_TouchTriggers (edict_t *ent); +void G_TouchSolids (edict_t *ent); + +char *G_CopyString (char *in); + +float *tv (float x, float y, float z); +char *vtos (vec3_t v); + +float vectoyaw (vec3_t vec); +void vectoangles (vec3_t vec, vec3_t angles); + +// +// g_combat.c +// +qboolean OnSameTeam (edict_t *ent1, edict_t *ent2); +qboolean CanDamage (edict_t *targ, edict_t *inflictor); +qboolean CheckTeamDamage (edict_t *targ, edict_t *attacker); +void T_Damage (edict_t *targ, edict_t *inflictor, edict_t *attacker, vec3_t dir, vec3_t point, vec3_t normal, int damage, int knockback, int dflags, int mod); +void T_RadiusDamage (edict_t *inflictor, edict_t *attacker, float damage, edict_t *ignore, float radius, int mod); + +// damage flags +#define DAMAGE_RADIUS 0x00000001 // damage was indirect +#define DAMAGE_NO_ARMOR 0x00000002 // armour does not protect from this damage +#define DAMAGE_ENERGY 0x00000004 // damage is from an energy based weapon +#define DAMAGE_NO_KNOCKBACK 0x00000008 // do not affect velocity, just view angles +#define DAMAGE_BULLET 0x00000010 // damage is from a bullet (used for ricochets) +#define DAMAGE_NO_PROTECTION 0x00000020 // armor, shields, invulnerability, and godmode have no effect + +#define DEFAULT_BULLET_HSPREAD 300 +#define DEFAULT_BULLET_VSPREAD 500 +#define DEFAULT_SHOTGUN_HSPREAD 1000 +#define DEFAULT_SHOTGUN_VSPREAD 500 +#define DEFAULT_DEATHMATCH_SHOTGUN_COUNT 12 +#define DEFAULT_SHOTGUN_COUNT 12 +#define DEFAULT_SSHOTGUN_COUNT 20 + +// +// g_monster.c +// +void monster_fire_bullet (edict_t *self, vec3_t start, vec3_t dir, int damage, int kick, int hspread, int vspread, int flashtype); +void monster_fire_shotgun (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick, int hspread, int vspread, int count, int flashtype); +void monster_fire_blaster (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int flashtype, int effect); +void monster_fire_grenade (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, int flashtype); +void monster_fire_rocket (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int flashtype); +void monster_fire_railgun (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick, int flashtype); +void monster_fire_bfg (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, int kick, float damage_radius, int flashtype); +void M_droptofloor (edict_t *ent); +void monster_think (edict_t *self); +void walkmonster_start (edict_t *self); +void swimmonster_start (edict_t *self); +void flymonster_start (edict_t *self); +void AttackFinished (edict_t *self, float time); +void monster_death_use (edict_t *self); +void M_CatagorizePosition (edict_t *ent); +qboolean M_CheckAttack (edict_t *self); +void M_FlyCheck (edict_t *self); +void M_CheckGround (edict_t *ent); + +// +// g_misc.c +// +void ThrowHead (edict_t *self, char *gibname, int damage, int type); +void ThrowClientHead (edict_t *self, int damage); +void ThrowGib (edict_t *self, char *gibname, int damage, int type); +void BecomeExplosion1(edict_t *self); + +// +// g_ai.c +// +void AI_SetSightClient (void); + +void ai_stand (edict_t *self, float dist); +void ai_move (edict_t *self, float dist); +void ai_walk (edict_t *self, float dist); +void ai_turn (edict_t *self, float dist); +void ai_run (edict_t *self, float dist); +void ai_charge (edict_t *self, float dist); +int range (edict_t *self, edict_t *other); + +void FoundTarget (edict_t *self); +qboolean infront (edict_t *self, edict_t *other); +qboolean visible (edict_t *self, edict_t *other); +qboolean FacingIdeal(edict_t *self); + +// +// g_weapon.c +// +void ThrowDebris (edict_t *self, char *modelname, float speed, vec3_t origin); +qboolean fire_hit (edict_t *self, vec3_t aim, int damage, int kick); +void fire_bullet (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick, int hspread, int vspread, int mod); +void fire_shotgun (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick, int hspread, int vspread, int count, int mod); +void fire_blaster (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, int effect, qboolean hyper); +void fire_grenade (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, float timer, float damage_radius); +void fire_grenade2 (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, float timer, float damage_radius, qboolean held); +void fire_rocket (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, float damage_radius, int radius_damage); +void fire_rail (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick); +void fire_bfg (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, float damage_radius); +// RAFAEL +void fire_ionripper (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, int effect); +void fire_heat (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, float damage_radius, int radius_damage); +void fire_blueblaster (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, int effect); +void fire_plasma (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, float damage_radius, int radius_damage); +void fire_trap (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, float timer, float damage_radius, qboolean held); + +// +// g_ptrail.c +// +void PlayerTrail_Init (void); +void PlayerTrail_Add (vec3_t spot); +void PlayerTrail_New (vec3_t spot); +edict_t *PlayerTrail_PickFirst (edict_t *self); +edict_t *PlayerTrail_PickNext (edict_t *self); +edict_t *PlayerTrail_LastSpot (void); + +// +// g_client.c +// +void respawn (edict_t *ent); +void BeginIntermission (edict_t *targ); +void PutClientInServer (edict_t *ent); +void InitClientPersistant (gclient_t *client); +void InitClientResp (gclient_t *client); +void InitBodyQue (void); +void ClientBeginServerFrame (edict_t *ent); + +// +// g_player.c +// +void player_pain (edict_t *self, edict_t *other, float kick, int damage); +void player_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point); + +// +// g_svcmds.c +// +void ServerCommand (void); +qboolean SV_FilterPacket (char *from); + +// +// p_view.c +// +void ClientEndServerFrame (edict_t *ent); + +// +// p_hud.c +// +void MoveClientToIntermission (edict_t *client); +void G_SetStats (edict_t *ent); +void G_SetSpectatorStats (edict_t *ent); +void G_CheckChaseStats (edict_t *ent); +void ValidateSelectedItem (edict_t *ent); +void DeathmatchScoreboardMessage (edict_t *client, edict_t *killer); + +// +// g_pweapon.c +// +void PlayerNoise(edict_t *who, vec3_t where, int type); +void P_ProjectSource (gclient_t *client, vec3_t point, vec3_t distance, vec3_t forward, vec3_t right, vec3_t result); +void Weapon_Generic (edict_t *ent, int FRAME_ACTIVATE_LAST, int FRAME_FIRE_LAST, int FRAME_IDLE_LAST, int FRAME_DEACTIVATE_LAST, int *pause_frames, int *fire_frames, void (*fire)(edict_t *ent)); + + +// +// m_move.c +// +qboolean M_CheckBottom (edict_t *ent); +qboolean M_walkmove (edict_t *ent, float yaw, float dist); +void M_MoveToGoal (edict_t *ent, float dist); +void M_ChangeYaw (edict_t *ent); + +// +// g_phys.c +// +void G_RunEntity (edict_t *ent); + +// +// g_main.c +// +void SaveClientData (void); +void FetchClientEntData (edict_t *ent); + +// +// g_chase.c +// +void UpdateChaseCam(edict_t *ent); +void ChaseNext(edict_t *ent); +void ChasePrev(edict_t *ent); +void GetChaseTarget(edict_t *ent); + +//============================================================================ + +// client_t->anim_priority +#define ANIM_BASIC 0 // stand / run +#define ANIM_WAVE 1 +#define ANIM_JUMP 2 +#define ANIM_PAIN 3 +#define ANIM_ATTACK 4 +#define ANIM_DEATH 5 + +// ### Hentai ### BEGIN +#define ANIM_REVERSE 6 +// ### Hentai ### END + +// client data that stays across multiple level loads +typedef struct +{ + char userinfo[MAX_INFO_STRING]; + char netname[16]; + int hand; + + qboolean connected; // a loadgame will leave valid entities that + // just don't have a connection yet + + // values saved and restored from edicts when changing levels + int health; + int max_health; + qboolean powerArmorActive; + + int selected_item; + int inventory[MAX_ITEMS]; + + // ammo capacities + int max_bullets; + int max_shells; + int max_rockets; + int max_grenades; + int max_cells; + int max_slugs; + // RAFAEL + int max_magslug; + int max_trap; + + gitem_t *weapon; + gitem_t *lastweapon; + + int power_cubes; // used for tracking the cubes in coop games + int score; // for calculating total unit score in coop games + + int game_helpchanged; + int helpchanged; + + qboolean spectator; // client is a spectator +} client_persistant_t; + +/* +//Zigock client info +#define ALEAT_MAX 10 + +typedef struct zgcl_s +{ + int zclass; //class no. + +// true client用 zoom フラグ + int aiming; //0-not 1-aiming 2-firing zoomingflag + float distance; //zoom中のFOV値 + float olddistance; //旧zooming FOV値 + qboolean autozoom; //autozoom + qboolean lockon; //lockon flag false-not true-locking + +// bot用 + int zcstate; //status + + int botskill; //skill + + //targets + edict_t *first_target; //enemy uses LockOntarget(for client) + edict_t *second_target; //kindof items + float targetlock; //target locking time + + + //waiting + vec3_t movtarget_pt; //moving target waiting point + edict_t *waitin_obj; //for waiting sequence complete + + //basical moving + float moveyaw; //true moving yaw + + //camp & aiming + float preaimingtime; + float campingtime; + + //combat + int total_bomb; //total put bomb + + float pp; + + edict_t *sighten; //sighting enemy to me info from entity sight + edict_t *locked; //locking enemy to me info from lockon missile + +} zgcl_t; + +*/ +#include "botstr.h" +// client data that stays across deathmatch respawns +typedef struct +{ + client_persistant_t coop_respawn; // what to set client->pers to on a respawn + int enterframe; // level.framenum the client entered the game + int score; // frags, etc +//ZOID + int ctf_team; // CTF team + int ctf_state; + float ctf_lasthurtcarrier; + float ctf_lastreturnedflag; + float ctf_flagsince; + float ctf_lastfraggedcarrier; + qboolean id_state; +//ZOID +//ponko + int context; +//ponko + vec3_t cmd_angles; // angles sent over in the last command + int game_helpchanged; + int helpchanged; + + qboolean spectator; // client is a spectator +} client_respawn_t; + +// this structure is cleared on each PutClientInServer(), +// except for 'client->pers' +struct gclient_s +{ + // known to server + player_state_t ps; // communicated by server to clients + int ping; + + // private to game + client_persistant_t pers; + client_respawn_t resp; + pmove_state_t old_pmove; // for detecting out-of-pmove changes + + qboolean showscores; // set layout stat +//ZOID + qboolean inmenu; // in menu + pmenuhnd_t *menu; // current menu +//ZOID + qboolean showinventory; // set layout stat + qboolean showhelp; + qboolean showhelpicon; + + int ammo_index; + + int buttons; + int oldbuttons; + int latched_buttons; + + qboolean weapon_thunk; + + gitem_t *newweapon; + + // sum up damage over an entire frame, so + // shotgun blasts give a single big kick + int damage_armor; // damage absorbed by armor + int damage_parmor; // damage absorbed by power armor + int damage_blood; // damage taken out of health + int damage_knockback; // impact damage + vec3_t damage_from; // origin for vector calculation + + float killer_yaw; // when dead, look at killer + + weaponstate_t weaponstate; + vec3_t kick_angles; // weapon kicks + vec3_t kick_origin; + float v_dmg_roll, v_dmg_pitch, v_dmg_time; // damage kicks + float fall_time, fall_value; // for view drop on fall + float damage_alpha; + float bonus_alpha; + vec3_t damage_blend; + vec3_t v_angle; // aiming direction + float bobtime; // so off-ground doesn't change it + vec3_t oldviewangles; + vec3_t oldvelocity; + + float next_drown_time; + int old_waterlevel; + int breather_sound; + + int machinegun_shots; // for weapon raising + + // animation vars + int anim_end; + int anim_priority; + qboolean anim_duck; + qboolean anim_run; + + // powerup timers + float quad_framenum; + float invincible_framenum; + float breather_framenum; + float enviro_framenum; + + qboolean grenade_blew_up; + float grenade_time; + // RAFAEL + float quadfire_framenum; + qboolean trap_blew_up; + float trap_time; + + int silencer_shots; + int weapon_sound; + + float pickup_msg_time; + + float respawn_time; // can respawn when time > this + +//ZOID + void *ctf_grapple; // entity of grapple + int ctf_grapplestate; // true if pulling + float ctf_grapplereleasetime; // time of grapple release + float ctf_regentime; // regen tech + float ctf_techsndtime; + float ctf_lasttechmsg; + edict_t *chase_target; + qboolean update_chase; +//ZOID + zgcl_t zc; //zigock client info +}; + + +struct edict_s +{ + entity_state_t s; + struct gclient_s *client; // NULL if not a player + // the server expects the first part + // of gclient_s to be a player_state_t + // but the rest of it is opaque + + qboolean inuse; + int linkcount; + + // FIXME: move these fields to a server private sv_entity_t + link_t area; // linked to a division node or leaf + + int num_clusters; // if -1, use headnode instead + int clusternums[MAX_ENT_CLUSTERS]; + int headnode; // unused if num_clusters != -1 + int areanum, areanum2; + + //================================ + + int svflags; + vec3_t mins, maxs; + vec3_t absmin, absmax, size; + solid_t solid; + int clipmask; + edict_t *owner; + + + // DO NOT MODIFY ANYTHING ABOVE THIS, THE SERVER + // EXPECTS THE FIELDS IN THAT ORDER! + + //================================ + int movetype; + int flags; + + char *model; + float freetime; // sv.time when the object was freed + + // + // only used locally in game, not by server + // + char *message; + char *classname; + int spawnflags; + + float timestamp; + + float angle; // set in qe3, -1 = up, -2 = down + char *target; + char *targetname; + char *killtarget; + char *team; + char *pathtarget; + char *deathtarget; + char *combattarget; + edict_t *target_ent; +//ponko + edict_t *union_ent; //union item + edict_t *trainteam; //train team + int arena; //arena +//ponko + float speed, accel, decel; + vec3_t movedir; + vec3_t pos1, pos2; + + vec3_t velocity; + vec3_t avelocity; + int mass; + float air_finished; + float gravity; // per entity gravity multiplier (1.0 is normal) + // use for lowgrav artifact, flares + + edict_t *goalentity; + edict_t *movetarget; + float yaw_speed; + float ideal_yaw; + + float nextthink; + void (*prethink) (edict_t *ent); + void (*think)(edict_t *self); + void (*blocked)(edict_t *self, edict_t *other); //move to moveinfo? + void (*touch)(edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf); + void (*use)(edict_t *self, edict_t *other, edict_t *activator); + void (*pain)(edict_t *self, edict_t *other, float kick, int damage); + void (*die)(edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point); + + float touch_debounce_time; // are all these legit? do we need more/less of them? + float pain_debounce_time; + float damage_debounce_time; + float fly_sound_debounce_time; //move to clientinfo + float last_move_time; + + int health; + int max_health; + int gib_health; + int deadflag; + qboolean show_hostile; + + float powerarmor_time; + + char *map; // target_changelevel + + int viewheight; // height above origin where eyesight is determined + int takedamage; + int dmg; + int radius_dmg; + float dmg_radius; + int sounds; //make this a spawntemp var? + int count; + + edict_t *chain; + edict_t *enemy; + edict_t *oldenemy; + edict_t *activator; + edict_t *groundentity; + int groundentity_linkcount; + edict_t *teamchain; + edict_t *teammaster; + + edict_t *mynoise; // can go in client only + edict_t *mynoise2; + + int noise_index; + int noise_index2; + float volume; + float attenuation; + + // timing variables + float wait; + float delay; // before firing targets + float random; + + float teleport_time; + + int watertype; + int waterlevel; + + vec3_t move_origin; + vec3_t move_angles; + + // move this to clientinfo? + int light_level; + + int style; // also used as areaportal number + + gitem_t *item; // for bonus items + + // common data blocks + moveinfo_t moveinfo; + monsterinfo_t monsterinfo; + + // RAFAEL + int orders; +}; + +//ZOID +#include "g_ctf.h" +//ZOID +#endif \ No newline at end of file diff --git a/src/g_main.c b/src/g_main.c new file mode 100644 index 0000000..a64a2e8 --- /dev/null +++ b/src/g_main.c @@ -0,0 +1,626 @@ + +#include "g_local.h" +#include "bot.h" + +game_locals_t game; +level_locals_t level; +game_import_t gi; +game_export_t globals; +spawn_temp_t st; + +int sm_meat_index; +int snd_fry; +int meansOfDeath; + +edict_t *g_edicts; + +cvar_t *deathmatch; +cvar_t *coop; +cvar_t *dmflags; +cvar_t *skill; +cvar_t *fraglimit; +cvar_t *timelimit; + +cvar_t *filterban; + +//ZOID +cvar_t *capturelimit; +//ZOID +cvar_t *password; +cvar_t *spectator_password; +cvar_t *maxclients; +cvar_t *maxspectators; +cvar_t *maxentities; +cvar_t *g_select_empty; +cvar_t *dedicated; + +cvar_t *sv_maxvelocity; +cvar_t *sv_gravity; + +cvar_t *sv_rollspeed; +cvar_t *sv_rollangle; +cvar_t *gun_x; +cvar_t *gun_y; +cvar_t *gun_z; + +cvar_t *run_pitch; +cvar_t *run_roll; +cvar_t *bob_up; +cvar_t *bob_pitch; +cvar_t *bob_roll; + +cvar_t *sv_cheats; + +//ponpoko +cvar_t *gamepath; +cvar_t *chedit; +cvar_t *vwep; +cvar_t *maplist; +cvar_t *botlist; +cvar_t *autospawn; +cvar_t *zigmode; +float spawncycle; +float ctfjob_update; +//ponpoko + +void SpawnEntities (char *mapname, char *entities, char *spawnpoint); +void ClientThink (edict_t *ent, usercmd_t *cmd); +qboolean ClientConnect (edict_t *ent, char *userinfo, qboolean loadgame); +void ClientUserinfoChanged (edict_t *ent, char *userinfo); +void ClientDisconnect (edict_t *ent); +void ClientBegin (edict_t *ent, qboolean loadgame); +void ClientCommand (edict_t *ent); +void RunEntity (edict_t *ent); +void WriteGame (char *filename); +void ReadGame (char *filename); +void WriteLevel (char *filename); +void ReadLevel (char *filename); +void InitGame (void); +void G_RunFrame (void); + +void SetBotFlag1(edict_t *ent); //チーム1の旗 +void SetBotFlag2(edict_t *ent); //チーム2の旗 + +//=================================================================== + + +/* +================= +GetGameAPI + +Returns a pointer to the structure with all entry points +and global variables +================= +*/ +void ShutdownGame (void) +{ + gi.dprintf ("==== ShutdownGame ====\n"); + +// Bot_LevelChange(); + + gi.FreeTags (TAG_LEVEL); + gi.FreeTags (TAG_GAME); + SetBotFlag1(NULL); + SetBotFlag2(NULL); +} + +//void Dummy (void) {}; + +game_export_t *GetGameAPI (game_import_t *import) +{ + gi = *import; + + globals.apiversion = GAME_API_VERSION; + globals.Init = InitGame; + globals.Shutdown = ShutdownGame; + globals.SpawnEntities = SpawnEntities; + + globals.WriteGame = WriteGame; + globals.ReadGame = ReadGame; + globals.WriteLevel = WriteLevel; + globals.ReadLevel = ReadLevel; + + globals.ClientThink = ClientThink; + globals.ClientConnect = ClientConnect; + globals.ClientUserinfoChanged = ClientUserinfoChanged; + globals.ClientDisconnect = ClientDisconnect; + globals.ClientBegin = ClientBegin; + globals.ClientCommand = ClientCommand; + + globals.RunFrame = G_RunFrame; + + globals.ServerCommand = ServerCommand; + + globals.edict_size = sizeof(edict_t); + + return &globals; +} + +#ifndef GAME_HARD_LINKED +// this is only here so the functions in q_shared.c and q_shwin.c can link +void Sys_Error (char *error, ...) +{ + va_list argptr; + char text[1024]; + + va_start (argptr, error); + vsprintf (text, error, argptr); + va_end (argptr); + + gi.error (ERR_FATAL, "%s", text); +} + +void Com_Printf (char *msg, ...) +{ + va_list argptr; + char text[1024]; + + va_start (argptr, msg); + vsprintf (text, msg, argptr); + va_end (argptr); + + gi.dprintf ("%s", text); +} + +#endif + +//====================================================================== + + +/* +================= +ClientEndServerFrames +================= +*/ +void ClientEndServerFrames (void) +{ + int i; + edict_t *ent; + + // calc the player views now that all pushing + // and damage has been added + for (i=0 ; ivalue ; i++) + { + ent = g_edicts + 1 + i; + if (!ent->inuse || !ent->client) + continue; + if(!(ent->svflags & SVF_MONSTER)) + ClientEndServerFrame (ent); + } + +} + + +/* +================= +GetNextMap + +get next map's file name +================= +*/ +void Get_NextMap() +{ + FILE *fp; + qboolean firstflag = false; + char Buff[MAX_QPATH]; + char top[MAX_QPATH]; + char nextmap[MAX_QPATH]; + int i; + + if(!maplist->string) return; + + sprintf(Buff,".\\%s\\3ZBMAPS.LST",gamepath->string); + fp = fopen(Buff,"r"); + if(fp == NULL) return; + + //search section + while(1) + { + if(fgets( Buff, sizeof(Buff), fp ) == NULL) goto NONEXTMAP; + + if(Buff[0] != '[') continue; + + i = 0; + while(1) + { + if(Buff[i] == ']') Buff[i] = 0; + + if(Buff[i] == 0) break; + + if(++i >= sizeof(Buff)) + { + Buff[i - 1] = 0; + break; + } + } + //compare map section name + if(Q_stricmp (&Buff[1], maplist->string) == 0) break; + } + + //search current mapname + while(1) + { + if(fgets( Buff, sizeof(Buff), fp ) == NULL) goto NONEXTMAP; + + if(Buff[0] == '[') + { + if( firstflag ) + { + strcpy(nextmap,top); + goto SETNEXTMAP; + } + else goto NONEXTMAP; + } + + if(Buff[0] == '\n') continue; + + sscanf(Buff,"%s",nextmap); + + if(!firstflag) + { + firstflag = true; + strcpy(top,nextmap); + } + + if(Q_stricmp (level.mapname, nextmap) == 0) break; + } + + //search nextmap + while(1) + { + if(fgets( Buff, sizeof(Buff), fp ) == NULL) + { + if( firstflag ) + { + strcpy(nextmap,top); + goto SETNEXTMAP; + } + else goto NONEXTMAP; + } + + if(Buff[0] == '[') + { + if( firstflag ) + { + strcpy(nextmap,top); + goto SETNEXTMAP; + } + else goto NONEXTMAP; + } + + if(Buff[0] == '\n') continue; + + sscanf(Buff,"%s",nextmap); + break; + } +SETNEXTMAP: + + strcpy(level.nextmap,nextmap); + +NONEXTMAP: + fclose(fp); + +} +/* +================= +EndDMLevel + +The timelimit or fraglimit has been exceeded +================= +*/ +void EndDMLevel (void) +{ + edict_t *ent; + + Get_NextMap(); + + // stay on same level flag + if ((int)dmflags->value & DF_SAME_LEVEL) + { + ent = G_Spawn (); + ent->classname = "target_changelevel"; + ent->map = level.mapname; + } + else if (level.nextmap) + { // go to a specific map + ent = G_Spawn (); + ent->classname = "target_changelevel"; + ent->map = level.nextmap; + } + else + { // search for a changeleve + ent = G_Find (NULL, FOFS(classname), "target_changelevel"); + if (!ent) + { // the map designer didn't include a changelevel, + // so create a fake ent that goes back to the same level + ent = G_Spawn (); + ent->classname = "target_changelevel"; + ent->map = level.mapname; + } + } + + BeginIntermission (ent); + +//PONKO + Bot_LevelChange(); +//PONKO +} + +/* +================= +CheckNeedPass +================= +*/ +void CheckNeedPass (void) +{ + int need; + + // if password or spectator_password has changed, update needpass + // as needed + if (password->modified || spectator_password->modified) + { + password->modified = spectator_password->modified = false; + + need = 0; + + if (*password->string && Q_stricmp(password->string, "none")) + need |= 1; + if (*spectator_password->string && Q_stricmp(spectator_password->string, "none")) + need |= 2; + + gi.cvar_set("needpass", va("%d", need)); + } +} + +/* +================= +CheckDMRules +================= +*/ +void CheckDMRules (void) +{ + int i; + gclient_t *cl; + + if (level.intermissiontime) + return; + + if (!deathmatch->value) + return; + + if (timelimit->value) + { + if (level.time >= timelimit->value*60) + { + gi.bprintf (PRINT_HIGH, "Timelimit hit.\n"); + EndDMLevel (); + return; + } + } + + if (fraglimit->value) + { +//ZOID + if (ctf->value) { + if (CTFCheckRules()) { + EndDMLevel (); + } + } +//ZOID + for (i=0 ; ivalue ; i++) + { + cl = game.clients + i; + if (!g_edicts[i+1].inuse) + continue; + + if (cl->resp.score >= fraglimit->value) + { + gi.bprintf (PRINT_HIGH, "Fraglimit hit.\n"); + EndDMLevel (); + return; + } + } + } +} + + +/* +============= +ExitLevel +============= +*/ +void ExitLevel (void) +{ + int i; + edict_t *ent; + char command [256]; + + Com_sprintf (command, sizeof(command), "gamemap \"%s\"\n", level.changemap); + gi.AddCommandString (command); + level.changemap = NULL; + level.exitintermission = 0; + level.intermissiontime = 0; + ClientEndServerFrames (); + + // clear some things before going to next level + for (i=0 ; ivalue ; i++) + { + ent = g_edicts + 1 + i; + if (!ent->inuse) + continue; + if (ent->health > ent->client->pers.max_health) + ent->health = ent->client->pers.max_health; + } + + SetBotFlag1(NULL); + SetBotFlag2(NULL); + +//ZOID + CTFInit(); +//ZOID +} + +/* +================ +G_RunFrame + +Advances the world by 0.1 seconds +================ +*/ + +void G_InitEdict (edict_t *e); + +void G_RunFrame (void) +{ + int i,j; + static int ofs; + static float next_fragadd = 0; + edict_t *ent; + + vec3_t v,vv; + qboolean haveflag; + gitem_t *item; + + level.framenum++; + level.time = level.framenum*FRAMETIME; + + // choose a client for monsters to target this frame +// AI_SetSightClient (); + + // exit intermissions + + if (level.exitintermission) + { + ExitLevel (); + return; + } + +// +// Bot Spawning +// + if(SpawnWaitingBots && !level.intermissiontime) + { + if(spawncycle < level.time) + { + Bot_SpawnCall(); + spawncycle = level.time + FRAMETIME * 10 + 0.01 * SpawnWaitingBots; + } + } + else + { + if(spawncycle < level.time) spawncycle = level.time + FRAMETIME * 10; + } + // + // treat each object in turn + // even the world gets a chance to think + // + haveflag = false; + ent = &g_edicts[0]; + for (i=0 ; iinuse) + continue; + + level.current_entity = ent; + + VectorCopy (ent->s.origin, ent->s.old_origin); + + // if the ground entity moved, make sure we are still on it + if ((ent->groundentity) && (ent->groundentity->linkcount != ent->groundentity_linkcount)) + { + ent->groundentity = NULL; + if ( !(ent->flags & (FL_SWIM|FL_FLY)) && (ent->svflags & SVF_MONSTER) ) + { + M_CheckGround (ent); + } + } + + //ctf job assign + if(ctf->value) + { + if(ctfjob_update < level.time) + { +//gi.bprintf(PRINT_HIGH,"Assigned!!!\n"); + CTFJobAssign(); + ctfjob_update = level.time + FRAMETIME * 2; + } + } +//////////旗のスコアチェック + if(zigmode->value == 1) + { + if(next_fragadd < level.time) + { + if(i > 0 && i <= maxclients->value && g_edicts[i].client) + { + if(g_edicts[i].client->pers.inventory[ITEM_INDEX(zflag_item)]) + { + zflag_ent = NULL; + haveflag = true; + gi.sound(ent, CHAN_VOICE, gi.soundindex("misc/secret.wav"), 1, ATTN_NORM, 0); + if (!((int)(dmflags->value) & (DF_MODELTEAMS | DF_SKINTEAMS))) + g_edicts[i].client->resp.score += 1; + else + { + //旗を持ってるとフラッグを足す + for ( j = 1 ; j <= maxclients->value ; j++) + { + if(g_edicts[j].inuse) + { + if(OnSameTeam(&g_edicts[i],&g_edicts[j])) + g_edicts[j].client->resp.score += 1; + } + } + } + } + } + if(zflag_ent != NULL) + { + if(!zflag_ent->inuse) + { + // item = FindItem("Zig Flag"); + SelectSpawnPoint (ent, v, vv); + // VectorCopy (v, ent->s.origin); + if(ZIGDrop_Flag(ent,zflag_item)) + { + VectorCopy (v, zflag_ent->s.origin); + } + } + } + } + } +///////////// + if (i > 0 && i <= maxclients->value && !(ent->svflags & SVF_MONSTER)) + { + ClientBeginServerFrame (ent); + continue; + } + + G_RunEntity (ent); + } + + if(next_fragadd < level.time) + { + if(zflag_ent == NULL && !haveflag && !ctf->value + && zigmode->value == 1 && zigflag_spawn == 2) + { + SelectSpawnPoint (ent, v, vv); + //VectorCopy (v, ent->s.origin); + if(ZIGDrop_Flag(ent,zflag_item)) + { + VectorCopy (v, zflag_ent->s.origin); + } + } + + next_fragadd = level.time + FRAMETIME * 100; + } + + // see if it is time to end a deathmatch + CheckDMRules (); + + // see if needpass needs updated + CheckNeedPass (); + + // build the playerstate_t structures for all players + ClientEndServerFrames (); +} + diff --git a/src/g_misc.c b/src/g_misc.c new file mode 100644 index 0000000..258527e --- /dev/null +++ b/src/g_misc.c @@ -0,0 +1,2060 @@ +// g_misc.c + +#include "g_local.h" +#include "bot.h" + +/*QUAKED func_group (0 0 0) ? +Used to group brushes together just for editor convenience. +*/ + +//===================================================== + +void Use_Areaportal (edict_t *ent, edict_t *other, edict_t *activator) +{ + ent->count ^= 1; // toggle state +// gi.dprintf ("portalstate: %i = %i\n", ent->style, ent->count); + gi.SetAreaPortalState (ent->style, ent->count); +} + +/*QUAKED func_areaportal (0 0 0) ? + +This is a non-visible object that divides the world into +areas that are seperated when this portal is not activated. +Usually enclosed in the middle of a door. +*/ +void SP_func_areaportal (edict_t *ent) +{ + ent->use = Use_Areaportal; + ent->count = 0; // always start closed; +} + +//===================================================== + + +/* +================= +Misc functions +================= +*/ +void VelocityForDamage (int damage, vec3_t v) +{ + v[0] = 100.0 * crandom(); + v[1] = 100.0 * crandom(); + v[2] = 200.0 + 100.0 * random(); + + if (damage < 50) + VectorScale (v, 0.7, v); + else + VectorScale (v, 1.2, v); +} + +void ClipGibVelocity (edict_t *ent) +{ + if (ent->velocity[0] < -300) + ent->velocity[0] = -300; + else if (ent->velocity[0] > 300) + ent->velocity[0] = 300; + if (ent->velocity[1] < -300) + ent->velocity[1] = -300; + else if (ent->velocity[1] > 300) + ent->velocity[1] = 300; + if (ent->velocity[2] < 200) + ent->velocity[2] = 200; // always some upwards + else if (ent->velocity[2] > 500) + ent->velocity[2] = 500; +} + + +/* +================= +gibs +================= +*/ +void gib_think (edict_t *self) +{ + self->s.frame++; + self->nextthink = level.time + FRAMETIME; + + if (self->s.frame == 10) + { + self->think = G_FreeEdict; + self->nextthink = level.time + 8 + random()*10; + } +} + +void gib_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + vec3_t normal_angles, right; + + if (!self->groundentity) + return; + + self->touch = NULL; + + if (plane) + { + gi.sound (self, CHAN_VOICE, gi.soundindex ("misc/fhit3.wav"), 1, ATTN_NORM, 0); + + vectoangles (plane->normal, normal_angles); + AngleVectors (normal_angles, NULL, right, NULL); + vectoangles (right, self->s.angles); + + if (self->s.modelindex == sm_meat_index) + { + self->s.frame++; + self->think = gib_think; + self->nextthink = level.time + FRAMETIME; + } + } +} + +void gib_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + G_FreeEdict (self); +} + +void ThrowGib (edict_t *self, char *gibname, int damage, int type) +{ + edict_t *gib; + vec3_t vd; + vec3_t origin; + vec3_t size; + float vscale; + + gib = G_Spawn(); + + VectorScale (self->size, 0.5, size); + VectorAdd (self->absmin, size, origin); + gib->s.origin[0] = origin[0] + crandom() * size[0]; + gib->s.origin[1] = origin[1] + crandom() * size[1]; + gib->s.origin[2] = origin[2] + crandom() * size[2]; + + gi.setmodel (gib, gibname); + gib->solid = SOLID_NOT; + gib->s.effects |= EF_GIB; + gib->flags |= FL_NO_KNOCKBACK; + gib->takedamage = DAMAGE_YES; + gib->die = gib_die; + + if (type == GIB_ORGANIC) + { + gib->movetype = MOVETYPE_TOSS; + gib->touch = gib_touch; + vscale = 0.5; + } + else + { + gib->movetype = MOVETYPE_BOUNCE; + vscale = 1.0; + } + + VelocityForDamage (damage, vd); + VectorMA (self->velocity, vscale, vd, gib->velocity); + ClipGibVelocity (gib); + gib->avelocity[0] = random()*600; + gib->avelocity[1] = random()*600; + gib->avelocity[2] = random()*600; + + gib->think = G_FreeEdict; + gib->nextthink = level.time + 10 + random()*10; + + gi.linkentity (gib); +} + +void ThrowHead (edict_t *self, char *gibname, int damage, int type) +{ + vec3_t vd; + float vscale; + + self->s.skinnum = 0; + self->s.frame = 0; + VectorClear (self->mins); + VectorClear (self->maxs); + + self->s.modelindex2 = 0; + gi.setmodel (self, gibname); + self->solid = SOLID_NOT; + self->s.effects |= EF_GIB; + self->s.effects &= ~EF_FLIES; + self->s.sound = 0; + self->flags |= FL_NO_KNOCKBACK; + self->svflags &= ~SVF_MONSTER; + self->takedamage = DAMAGE_YES; + self->die = gib_die; + + if (type == GIB_ORGANIC) + { + self->movetype = MOVETYPE_TOSS; + self->touch = gib_touch; + vscale = 0.5; + } + else + { + self->movetype = MOVETYPE_BOUNCE; + vscale = 1.0; + } + + VelocityForDamage (damage, vd); + VectorMA (self->velocity, vscale, vd, self->velocity); + ClipGibVelocity (self); + + self->avelocity[YAW] = crandom()*600; + + self->think = G_FreeEdict; + self->nextthink = level.time + 10 + random()*10; + + gi.linkentity (self); +} + + + +void ThrowHead2 (edict_t *self, char *gibname, int damage, int type) +{ + vec3_t vd; + float vscale; + + self->s.skinnum = 0; + self->s.frame = 0; + VectorClear (self->mins); + VectorClear (self->maxs); + + self->s.modelindex2 = 0; + gi.setmodel (self, gibname); + self->solid = SOLID_NOT; + self->s.effects |= EF_GIB; + self->s.effects &= ~EF_FLIES; + self->s.sound = 0; + self->flags |= FL_NO_KNOCKBACK; +// self->svflags &= ~SVF_MONSTER; + self->takedamage = DAMAGE_YES; + self->die = gib_die; + + if (type == GIB_ORGANIC) + { + self->movetype = MOVETYPE_TOSS; + self->touch = gib_touch; + vscale = 0.5; + } + else + { + self->movetype = MOVETYPE_BOUNCE; + vscale = 1.0; + } + + VelocityForDamage (damage, vd); + VectorMA (self->velocity, vscale, vd, self->velocity); + ClipGibVelocity (self); + + self->avelocity[YAW] = crandom()*600; + +// self->think = G_FreeEdict; +// self->nextthink = level.time + 10 + random()*10; + + gi.linkentity (self); +} + +void ThrowClientHead (edict_t *self, int damage) +{ + vec3_t vd; + char *gibname; + + if (rand()&1) + { + gibname = "models/objects/gibs/head2/tris.md2"; + self->s.skinnum = 1; // second skin is player + } + else + { + gibname = "models/objects/gibs/skull/tris.md2"; + self->s.skinnum = 0; + } + + self->s.origin[2] += 32; + self->s.frame = 0; + gi.setmodel (self, gibname); + VectorSet (self->mins, -16, -16, 0); + VectorSet (self->maxs, 16, 16, 16); + + self->takedamage = DAMAGE_NO; + self->solid = SOLID_NOT; + self->s.effects = EF_GIB; + self->s.sound = 0; + self->flags |= FL_NO_KNOCKBACK; + + self->movetype = MOVETYPE_BOUNCE; + VelocityForDamage (damage, vd); + VectorAdd (self->velocity, vd, self->velocity); + + if (self->client) // bodies in the queue don't have a client anymore + { + if(!(self->svflags & SVF_MONSTER)) + { + self->client->anim_priority = ANIM_DEATH; + self->client->anim_end = self->s.frame; + } + else + { + self->s.modelindex2 = 0; + self->s.modelindex3 = 0; + self->s.frame = 0; + self->client->anim_end = self->s.frame; + } + } + else + { + self->think = NULL; + self->nextthink = 0; + } + + gi.linkentity (self); +} + + +/* +================= +debris +================= +*/ +void debris_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + G_FreeEdict (self); +} + +void ThrowDebris (edict_t *self, char *modelname, float speed, vec3_t origin) +{ + edict_t *chunk; + vec3_t v; + + chunk = G_Spawn(); + VectorCopy (origin, chunk->s.origin); + gi.setmodel (chunk, modelname); + v[0] = 100 * crandom(); + v[1] = 100 * crandom(); + v[2] = 100 + 100 * crandom(); + VectorMA (self->velocity, speed, v, chunk->velocity); + chunk->movetype = MOVETYPE_BOUNCE; + chunk->solid = SOLID_NOT; + chunk->avelocity[0] = random()*600; + chunk->avelocity[1] = random()*600; + chunk->avelocity[2] = random()*600; + chunk->think = G_FreeEdict; + chunk->nextthink = level.time + 5 + random()*5; + chunk->s.frame = 0; + chunk->flags = 0; + chunk->classname = "debris"; + chunk->takedamage = DAMAGE_YES; + chunk->die = debris_die; + gi.linkentity (chunk); +} + + +void BecomeExplosion1 (edict_t *self) +{ +//ZOID + //flags are important + if (strcmp(self->classname, "item_flag_team1") == 0) { + CTFResetFlag(CTF_TEAM1); // this will free self! + gi.bprintf(PRINT_HIGH, "The %s flag has returned!\n", + CTFTeamName(CTF_TEAM1)); + return; + } + if (strcmp(self->classname, "item_flag_team2") == 0) { + CTFResetFlag(CTF_TEAM2); // this will free self! + gi.bprintf(PRINT_HIGH, "The %s flag has returned!\n", + CTFTeamName(CTF_TEAM1)); + return; + } + // techs are important too + if (self->item && (self->item->flags & IT_TECH)) { + CTFRespawnTech(self); // this frees self! + return; + } +//ZOID + + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_EXPLOSION1); + gi.WritePosition (self->s.origin); + gi.multicast (self->s.origin, MULTICAST_PVS); + + G_FreeEdict (self); +} + + +void BecomeExplosion2 (edict_t *self) +{ + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_EXPLOSION2); + gi.WritePosition (self->s.origin); + gi.multicast (self->s.origin, MULTICAST_PVS); + + G_FreeEdict (self); +} + + +/*QUAKED path_corner (.5 .3 0) (-8 -8 -8) (8 8 8) TELEPORT +Target: next path corner +Pathtarget: gets used when an entity that has + this path_corner targeted touches it +*/ + +void path_corner_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + vec3_t v; + edict_t *next; + + if (other->movetarget != self) + return; + + if (other->enemy) + return; + + if (self->pathtarget) + { + char *savetarget; + + savetarget = self->target; + self->target = self->pathtarget; + G_UseTargets (self, other); + self->target = savetarget; + } + + if (self->target) + next = G_PickTarget(self->target); + else + next = NULL; + + if ((next) && (next->spawnflags & 1)) + { + VectorCopy (next->s.origin, v); + v[2] += next->mins[2]; + v[2] -= other->mins[2]; + VectorCopy (v, other->s.origin); + next = G_PickTarget(next->target); + } + + other->goalentity = other->movetarget = next; + + if (self->wait) + { + other->monsterinfo.pausetime = level.time + self->wait; + other->monsterinfo.stand (other); + return; + } + + if (!other->movetarget) + { + other->monsterinfo.pausetime = level.time + 100000000; + other->monsterinfo.stand (other); + } + else + { + VectorSubtract (other->goalentity->s.origin, other->s.origin, v); + other->ideal_yaw = vectoyaw (v); + } +} + +void SP_path_corner (edict_t *self) +{ + if (!self->targetname) + { + gi.dprintf ("path_corner with no targetname at %s\n", vtos(self->s.origin)); + G_FreeEdict (self); + return; + } + + self->solid = SOLID_TRIGGER; + self->touch = path_corner_touch; + VectorSet (self->mins, -8, -8, -8); + VectorSet (self->maxs, 8, 8, 8); + self->svflags |= SVF_NOCLIENT; + gi.linkentity (self); +} + + +/*QUAKED point_combat (0.5 0.3 0) (-8 -8 -8) (8 8 8) Hold +Makes this the target of a monster and it will head here +when first activated before going after the activator. If +hold is selected, it will stay here. +*/ +void point_combat_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + edict_t *activator; + + if (other->movetarget != self) + return; + + if (self->target) + { + other->target = self->target; + other->goalentity = other->movetarget = G_PickTarget(other->target); + if (!other->goalentity) + { + gi.dprintf("%s at %s target %s does not exist\n", self->classname, vtos(self->s.origin), self->target); + other->movetarget = self; + } + self->target = NULL; + } + else if ((self->spawnflags & 1) && !(other->flags & (FL_SWIM|FL_FLY))) + { + other->monsterinfo.pausetime = level.time + 100000000; + other->monsterinfo.aiflags |= AI_STAND_GROUND; + other->monsterinfo.stand (other); + } + + if (other->movetarget == self) + { + other->target = NULL; + other->movetarget = NULL; + other->goalentity = other->enemy; + other->monsterinfo.aiflags &= ~AI_COMBAT_POINT; + } + + if (self->pathtarget) + { + char *savetarget; + + savetarget = self->target; + self->target = self->pathtarget; + if (other->enemy && other->enemy->client) + activator = other->enemy; + else if (other->oldenemy && other->oldenemy->client) + activator = other->oldenemy; + else if (other->activator && other->activator->client) + activator = other->activator; + else + activator = other; + G_UseTargets (self, activator); + self->target = savetarget; + } +} + +void SP_point_combat (edict_t *self) +{ + if (deathmatch->value) + { + G_FreeEdict (self); + return; + } + self->solid = SOLID_TRIGGER; + self->touch = point_combat_touch; + VectorSet (self->mins, -8, -8, -16); + VectorSet (self->maxs, 8, 8, 16); + self->svflags = SVF_NOCLIENT; + gi.linkentity (self); +}; + + +/*QUAKED viewthing (0 .5 .8) (-8 -8 -8) (8 8 8) +Just for the debugging level. Don't use +*/ +void TH_viewthing(edict_t *ent) +{ + ent->s.frame = (ent->s.frame + 1) % 7; + ent->nextthink = level.time + FRAMETIME; +} + +void SP_viewthing(edict_t *ent) +{ + gi.dprintf ("viewthing spawned\n"); + + ent->movetype = MOVETYPE_NONE; + ent->solid = SOLID_BBOX; + ent->s.renderfx = RF_FRAMELERP; + VectorSet (ent->mins, -16, -16, -24); + VectorSet (ent->maxs, 16, 16, 32); + ent->s.modelindex = gi.modelindex ("models/objects/banner/tris.md2"); + gi.linkentity (ent); + ent->nextthink = level.time + 0.5; + ent->think = TH_viewthing; + return; +} + + +/*QUAKED info_null (0 0.5 0) (-4 -4 -4) (4 4 4) +Used as a positional target for spotlights, etc. +*/ +void SP_info_null (edict_t *self) +{ + G_FreeEdict (self); +}; + + +/*QUAKED info_notnull (0 0.5 0) (-4 -4 -4) (4 4 4) +Used as a positional target for lightning. +*/ +void SP_info_notnull (edict_t *self) +{ + VectorCopy (self->s.origin, self->absmin); + VectorCopy (self->s.origin, self->absmax); +}; + + +/*QUAKED light (0 1 0) (-8 -8 -8) (8 8 8) START_OFF +Non-displayed light. +Default light value is 300. +Default style is 0. +If targeted, will toggle between on and off. +Default _cone value is 10 (used to set size of light for spotlights) +*/ + +#define START_OFF 1 + +static void light_use (edict_t *self, edict_t *other, edict_t *activator) +{ + if (self->spawnflags & START_OFF) + { + gi.configstring (CS_LIGHTS+self->style, "m"); + self->spawnflags &= ~START_OFF; + } + else + { + gi.configstring (CS_LIGHTS+self->style, "a"); + self->spawnflags |= START_OFF; + } +} + +void SP_light (edict_t *self) +{ + // no targeted lights in deathmatch, because they cause global messages + if (!self->targetname || deathmatch->value) + { + G_FreeEdict (self); + return; + } + + if (self->style >= 32) + { + self->use = light_use; + if (self->spawnflags & START_OFF) + gi.configstring (CS_LIGHTS+self->style, "a"); + else + gi.configstring (CS_LIGHTS+self->style, "m"); + } +} + + +/*QUAKED func_wall (0 .5 .8) ? TRIGGER_SPAWN TOGGLE START_ON ANIMATED ANIMATED_FAST +This is just a solid wall if not inhibited + +TRIGGER_SPAWN the wall will not be present until triggered + it will then blink in to existance; it will + kill anything that was in it's way + +TOGGLE only valid for TRIGGER_SPAWN walls + this allows the wall to be turned on and off + +START_ON only valid for TRIGGER_SPAWN walls + the wall will initially be present +*/ + +void func_wall_use (edict_t *self, edict_t *other, edict_t *activator) +{ + if (self->solid == SOLID_NOT) + { + self->solid = SOLID_BSP; + self->svflags &= ~SVF_NOCLIENT; + KillBox (self); + } + else + { + self->solid = SOLID_NOT; + self->svflags |= SVF_NOCLIENT; + } + gi.linkentity (self); + + if (!(self->spawnflags & 2)) + self->use = NULL; +} + +void SP_func_wall (edict_t *self) +{ + self->movetype = MOVETYPE_PUSH; + gi.setmodel (self, self->model); + + if (self->spawnflags & 8) + self->s.effects |= EF_ANIM_ALL; + if (self->spawnflags & 16) + self->s.effects |= EF_ANIM_ALLFAST; + + // just a wall + if ((self->spawnflags & 7) == 0) + { + self->solid = SOLID_BSP; + gi.linkentity (self); + return; + } + + // it must be TRIGGER_SPAWN + if (!(self->spawnflags & 1)) + { +// gi.dprintf("func_wall missing TRIGGER_SPAWN\n"); + self->spawnflags |= 1; + } + + // yell if the spawnflags are odd + if (self->spawnflags & 4) + { + if (!(self->spawnflags & 2)) + { + gi.dprintf("func_wall START_ON without TOGGLE\n"); + self->spawnflags |= 2; + } + } + + self->use = func_wall_use; + if (self->spawnflags & 4) + { + self->solid = SOLID_BSP; + } + else + { + self->solid = SOLID_NOT; + self->svflags |= SVF_NOCLIENT; + } + gi.linkentity (self); +} + + +/*QUAKED func_object (0 .5 .8) ? TRIGGER_SPAWN ANIMATED ANIMATED_FAST +This is solid bmodel that will fall if it's support it removed. +*/ + +void func_object_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + // only squash thing we fall on top of + if (!plane) + return; + if (plane->normal[2] < 1.0) + return; + if (other->takedamage == DAMAGE_NO) + return; + T_Damage (other, self, self, vec3_origin, self->s.origin, vec3_origin, self->dmg, 1, 0, MOD_CRUSH); +} + +void func_object_release (edict_t *self) +{ + self->movetype = MOVETYPE_TOSS; + self->touch = func_object_touch; +} + +void func_object_use (edict_t *self, edict_t *other, edict_t *activator) +{ + self->solid = SOLID_BSP; + self->svflags &= ~SVF_NOCLIENT; + self->use = NULL; + KillBox (self); + func_object_release (self); +} + +void SP_func_object (edict_t *self) +{ + gi.setmodel (self, self->model); + + self->mins[0] += 1; + self->mins[1] += 1; + self->mins[2] += 1; + self->maxs[0] -= 1; + self->maxs[1] -= 1; + self->maxs[2] -= 1; + + if (!self->dmg) + self->dmg = 100; + + if (self->spawnflags == 0) + { + self->solid = SOLID_BSP; + self->movetype = MOVETYPE_PUSH; + self->think = func_object_release; + self->nextthink = level.time + 2 * FRAMETIME; + } + else + { + self->solid = SOLID_NOT; + self->movetype = MOVETYPE_PUSH; + self->use = func_object_use; + self->svflags |= SVF_NOCLIENT; + } + + if (self->spawnflags & 2) + self->s.effects |= EF_ANIM_ALL; + if (self->spawnflags & 4) + self->s.effects |= EF_ANIM_ALLFAST; + + self->clipmask = MASK_MONSTERSOLID; + + gi.linkentity (self); +} + + +/*QUAKED func_explosive (0 .5 .8) ? Trigger_Spawn ANIMATED ANIMATED_FAST +Any brush that you want to explode or break apart. If you want an +ex0plosion, set dmg and it will do a radius explosion of that amount +at the center of the bursh. + +If targeted it will not be shootable. + +health defaults to 100. + +mass defaults to 75. This determines how much debris is emitted when +it explodes. You get one large chunk per 100 of mass (up to 8) and +one small chunk per 25 of mass (up to 16). So 800 gives the most. +*/ +void func_explosive_explode (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + vec3_t origin; + vec3_t chunkorigin; + vec3_t size; + int count; + int mass; + + // bmodel origins are (0 0 0), we need to adjust that here + VectorScale (self->size, 0.5, size); + VectorAdd (self->absmin, size, origin); + VectorCopy (origin, self->s.origin); + + self->takedamage = DAMAGE_NO; + + if (self->dmg) + T_RadiusDamage (self, attacker, self->dmg, NULL, self->dmg+40, MOD_EXPLOSIVE); + + VectorSubtract (self->s.origin, inflictor->s.origin, self->velocity); + VectorNormalize (self->velocity); + VectorScale (self->velocity, 150, self->velocity); + + // start chunks towards the center + VectorScale (size, 0.5, size); + + mass = self->mass; + if (!mass) + mass = 75; + + // big chunks + if (mass >= 100) + { + count = mass / 100; + if (count > 8) + count = 8; + while(count--) + { + chunkorigin[0] = origin[0] + crandom() * size[0]; + chunkorigin[1] = origin[1] + crandom() * size[1]; + chunkorigin[2] = origin[2] + crandom() * size[2]; + ThrowDebris (self, "models/objects/debris1/tris.md2", 1, chunkorigin); + } + } + + // small chunks + count = mass / 25; + if (count > 16) + count = 16; + while(count--) + { + chunkorigin[0] = origin[0] + crandom() * size[0]; + chunkorigin[1] = origin[1] + crandom() * size[1]; + chunkorigin[2] = origin[2] + crandom() * size[2]; + ThrowDebris (self, "models/objects/debris2/tris.md2", 2, chunkorigin); + } + + G_UseTargets (self, attacker); + + if (self->dmg) + BecomeExplosion1 (self); + else + G_FreeEdict (self); +} + +void func_explosive_use(edict_t *self, edict_t *other, edict_t *activator) +{ + func_explosive_explode (self, self, other, self->health, vec3_origin); +} + +void func_explosive_spawn (edict_t *self, edict_t *other, edict_t *activator) +{ + self->solid = SOLID_BSP; + self->svflags &= ~SVF_NOCLIENT; + self->use = NULL; + KillBox (self); + gi.linkentity (self); +} + +void SP_func_explosive (edict_t *self) +{ + if (deathmatch->value) + { // auto-remove for deathmatch + G_FreeEdict (self); + return; + } + + self->movetype = MOVETYPE_PUSH; + + gi.modelindex ("models/objects/debris1/tris.md2"); + gi.modelindex ("models/objects/debris2/tris.md2"); + + gi.setmodel (self, self->model); + + if (self->spawnflags & 1) + { + self->svflags |= SVF_NOCLIENT; + self->solid = SOLID_NOT; + self->use = func_explosive_spawn; + } + else + { + self->solid = SOLID_BSP; + if (self->targetname) + self->use = func_explosive_use; + } + + if (self->spawnflags & 2) + self->s.effects |= EF_ANIM_ALL; + if (self->spawnflags & 4) + self->s.effects |= EF_ANIM_ALLFAST; + + if (self->use != func_explosive_use) + { + if (!self->health) + self->health = 100; + self->die = func_explosive_explode; + self->takedamage = DAMAGE_YES; + } + + gi.linkentity (self); +} + + +/*QUAKED misc_explobox (0 .5 .8) (-16 -16 0) (16 16 40) +Large exploding box. You can override its mass (100), +health (80), and dmg (150). +*/ + +void barrel_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) + +{ + float ratio; + vec3_t v; + + if ((!other->groundentity) || (other->groundentity == self)) + return; + + ratio = (float)other->mass / (float)self->mass; + VectorSubtract (self->s.origin, other->s.origin, v); +// M_walkmove (self, vectoyaw(v), 20 * ratio * FRAMETIME); +} + +void barrel_explode (edict_t *self) +{ + vec3_t org; + float spd; + vec3_t save; + + T_RadiusDamage (self, self->activator, self->dmg, NULL, self->dmg+40, MOD_BARREL); + + VectorCopy (self->s.origin, save); + VectorMA (self->absmin, 0.5, self->size, self->s.origin); + + // a few big chunks + spd = 1.5 * (float)self->dmg / 200.0; + org[0] = self->s.origin[0] + crandom() * self->size[0]; + org[1] = self->s.origin[1] + crandom() * self->size[1]; + org[2] = self->s.origin[2] + crandom() * self->size[2]; + ThrowDebris (self, "models/objects/debris1/tris.md2", spd, org); + org[0] = self->s.origin[0] + crandom() * self->size[0]; + org[1] = self->s.origin[1] + crandom() * self->size[1]; + org[2] = self->s.origin[2] + crandom() * self->size[2]; + ThrowDebris (self, "models/objects/debris1/tris.md2", spd, org); + + // bottom corners + spd = 1.75 * (float)self->dmg / 200.0; + VectorCopy (self->absmin, org); + ThrowDebris (self, "models/objects/debris3/tris.md2", spd, org); + VectorCopy (self->absmin, org); + org[0] += self->size[0]; + ThrowDebris (self, "models/objects/debris3/tris.md2", spd, org); + VectorCopy (self->absmin, org); + org[1] += self->size[1]; + ThrowDebris (self, "models/objects/debris3/tris.md2", spd, org); + VectorCopy (self->absmin, org); + org[0] += self->size[0]; + org[1] += self->size[1]; + ThrowDebris (self, "models/objects/debris3/tris.md2", spd, org); + + // a bunch of little chunks + spd = 2 * self->dmg / 200; + org[0] = self->s.origin[0] + crandom() * self->size[0]; + org[1] = self->s.origin[1] + crandom() * self->size[1]; + org[2] = self->s.origin[2] + crandom() * self->size[2]; + ThrowDebris (self, "models/objects/debris2/tris.md2", spd, org); + org[0] = self->s.origin[0] + crandom() * self->size[0]; + org[1] = self->s.origin[1] + crandom() * self->size[1]; + org[2] = self->s.origin[2] + crandom() * self->size[2]; + ThrowDebris (self, "models/objects/debris2/tris.md2", spd, org); + org[0] = self->s.origin[0] + crandom() * self->size[0]; + org[1] = self->s.origin[1] + crandom() * self->size[1]; + org[2] = self->s.origin[2] + crandom() * self->size[2]; + ThrowDebris (self, "models/objects/debris2/tris.md2", spd, org); + org[0] = self->s.origin[0] + crandom() * self->size[0]; + org[1] = self->s.origin[1] + crandom() * self->size[1]; + org[2] = self->s.origin[2] + crandom() * self->size[2]; + ThrowDebris (self, "models/objects/debris2/tris.md2", spd, org); + org[0] = self->s.origin[0] + crandom() * self->size[0]; + org[1] = self->s.origin[1] + crandom() * self->size[1]; + org[2] = self->s.origin[2] + crandom() * self->size[2]; + ThrowDebris (self, "models/objects/debris2/tris.md2", spd, org); + org[0] = self->s.origin[0] + crandom() * self->size[0]; + org[1] = self->s.origin[1] + crandom() * self->size[1]; + org[2] = self->s.origin[2] + crandom() * self->size[2]; + ThrowDebris (self, "models/objects/debris2/tris.md2", spd, org); + org[0] = self->s.origin[0] + crandom() * self->size[0]; + org[1] = self->s.origin[1] + crandom() * self->size[1]; + org[2] = self->s.origin[2] + crandom() * self->size[2]; + ThrowDebris (self, "models/objects/debris2/tris.md2", spd, org); + org[0] = self->s.origin[0] + crandom() * self->size[0]; + org[1] = self->s.origin[1] + crandom() * self->size[1]; + org[2] = self->s.origin[2] + crandom() * self->size[2]; + ThrowDebris (self, "models/objects/debris2/tris.md2", spd, org); + + VectorCopy (save, self->s.origin); + if (self->groundentity) + BecomeExplosion2 (self); + else + BecomeExplosion1 (self); +} + +void barrel_delay (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + self->takedamage = DAMAGE_NO; + self->nextthink = level.time + 2 * FRAMETIME; + self->think = barrel_explode; + self->activator = attacker; +} + +void SP_misc_explobox (edict_t *self) +{ + if (deathmatch->value) + { // auto-remove for deathmatch + G_FreeEdict (self); + return; + } + + gi.modelindex ("models/objects/debris1/tris.md2"); + gi.modelindex ("models/objects/debris2/tris.md2"); + gi.modelindex ("models/objects/debris3/tris.md2"); + + self->solid = SOLID_BBOX; + self->movetype = MOVETYPE_STEP; + + self->model = "models/objects/barrels/tris.md2"; + self->s.modelindex = gi.modelindex (self->model); + VectorSet (self->mins, -16, -16, 0); + VectorSet (self->maxs, 16, 16, 40); + + if (!self->mass) + self->mass = 400; + if (!self->health) + self->health = 10; + if (!self->dmg) + self->dmg = 150; + + self->die = barrel_delay; + self->takedamage = DAMAGE_YES; + self->monsterinfo.aiflags = AI_NOSTEP; + + self->touch = barrel_touch; + + self->think = M_droptofloor; + self->nextthink = level.time + 2 * FRAMETIME; + + gi.linkentity (self); +} + + +// +// miscellaneous specialty items +// + +/*QUAKED misc_blackhole (1 .5 0) (-8 -8 -8) (8 8 8) +*/ + +void misc_blackhole_use (edict_t *ent, edict_t *other, edict_t *activator) +{ + /* + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_BOSSTPORT); + gi.WritePosition (ent->s.origin); + gi.multicast (ent->s.origin, MULTICAST_PVS); + */ + G_FreeEdict (ent); +} + +void misc_blackhole_think (edict_t *self) +{ + if (++self->s.frame < 19) + self->nextthink = level.time + FRAMETIME; + else + { + self->s.frame = 0; + self->nextthink = level.time + FRAMETIME; + } +} + +void SP_misc_blackhole (edict_t *ent) +{ + ent->movetype = MOVETYPE_NONE; + ent->solid = SOLID_NOT; + VectorSet (ent->mins, -64, -64, 0); + VectorSet (ent->maxs, 64, 64, 8); + ent->s.modelindex = gi.modelindex ("models/objects/black/tris.md2"); + ent->s.renderfx = RF_TRANSLUCENT; + ent->use = misc_blackhole_use; + ent->think = misc_blackhole_think; + ent->nextthink = level.time + 2 * FRAMETIME; + gi.linkentity (ent); +} + +/*QUAKED misc_eastertank (1 .5 0) (-32 -32 -16) (32 32 32) +*/ + +void misc_eastertank_think (edict_t *self) +{ + if (++self->s.frame < 293) + self->nextthink = level.time + FRAMETIME; + else + { + self->s.frame = 254; + self->nextthink = level.time + FRAMETIME; + } +} + +void SP_misc_eastertank (edict_t *ent) +{ + ent->movetype = MOVETYPE_NONE; + ent->solid = SOLID_BBOX; + VectorSet (ent->mins, -32, -32, -16); + VectorSet (ent->maxs, 32, 32, 32); + ent->s.modelindex = gi.modelindex ("models/monsters/tank/tris.md2"); + ent->s.frame = 254; + ent->think = misc_eastertank_think; + ent->nextthink = level.time + 2 * FRAMETIME; + gi.linkentity (ent); +} + +/*QUAKED misc_easterchick (1 .5 0) (-32 -32 0) (32 32 32) +*/ + + +void misc_easterchick_think (edict_t *self) +{ + if (++self->s.frame < 247) + self->nextthink = level.time + FRAMETIME; + else + { + self->s.frame = 208; + self->nextthink = level.time + FRAMETIME; + } +} + +void SP_misc_easterchick (edict_t *ent) +{ + ent->movetype = MOVETYPE_NONE; + ent->solid = SOLID_BBOX; + VectorSet (ent->mins, -32, -32, 0); + VectorSet (ent->maxs, 32, 32, 32); + ent->s.modelindex = gi.modelindex ("models/monsters/bitch/tris.md2"); + ent->s.frame = 208; + ent->think = misc_easterchick_think; + ent->nextthink = level.time + 2 * FRAMETIME; + gi.linkentity (ent); +} + +/*QUAKED misc_easterchick2 (1 .5 0) (-32 -32 0) (32 32 32) +*/ + + +void misc_easterchick2_think (edict_t *self) +{ + if (++self->s.frame < 287) + self->nextthink = level.time + FRAMETIME; + else + { + self->s.frame = 248; + self->nextthink = level.time + FRAMETIME; + } +} + +void SP_misc_easterchick2 (edict_t *ent) +{ + ent->movetype = MOVETYPE_NONE; + ent->solid = SOLID_BBOX; + VectorSet (ent->mins, -32, -32, 0); + VectorSet (ent->maxs, 32, 32, 32); + ent->s.modelindex = gi.modelindex ("models/monsters/bitch/tris.md2"); + ent->s.frame = 248; + ent->think = misc_easterchick2_think; + ent->nextthink = level.time + 2 * FRAMETIME; + gi.linkentity (ent); +} + + +/*QUAKED monster_commander_body (1 .5 0) (-32 -32 0) (32 32 48) +Not really a monster, this is the Tank Commander's decapitated body. +There should be a item_commander_head that has this as it's target. +*/ + +void commander_body_think (edict_t *self) +{ + if (++self->s.frame < 24) + self->nextthink = level.time + FRAMETIME; + else + self->nextthink = 0; + + if (self->s.frame == 22) + gi.sound (self, CHAN_BODY, gi.soundindex ("tank/thud.wav"), 1, ATTN_NORM, 0); +} + +void commander_body_use (edict_t *self, edict_t *other, edict_t *activator) +{ + self->think = commander_body_think; + self->nextthink = level.time + FRAMETIME; + gi.sound (self, CHAN_BODY, gi.soundindex ("tank/pain.wav"), 1, ATTN_NORM, 0); +} + +void commander_body_drop (edict_t *self) +{ + self->movetype = MOVETYPE_TOSS; + self->s.origin[2] += 2; +} + +void SP_monster_commander_body (edict_t *self) +{ + self->movetype = MOVETYPE_NONE; + self->solid = SOLID_BBOX; + self->model = "models/monsters/commandr/tris.md2"; + self->s.modelindex = gi.modelindex (self->model); + VectorSet (self->mins, -32, -32, 0); + VectorSet (self->maxs, 32, 32, 48); + self->use = commander_body_use; + self->takedamage = DAMAGE_YES; + self->flags = FL_GODMODE; + self->s.renderfx |= RF_FRAMELERP; + gi.linkentity (self); + + gi.soundindex ("tank/thud.wav"); + gi.soundindex ("tank/pain.wav"); + + self->think = commander_body_drop; + self->nextthink = level.time + 5 * FRAMETIME; +} + + +/*QUAKED misc_banner (1 .5 0) (-4 -4 -4) (4 4 4) +The origin is the bottom of the banner. +The banner is 128 tall. +*/ +void misc_banner_think (edict_t *ent) +{ + ent->s.frame = (ent->s.frame + 1) % 16; + ent->nextthink = level.time + FRAMETIME; +} + +void SP_misc_banner (edict_t *ent) +{ + ent->movetype = MOVETYPE_NONE; + ent->solid = SOLID_NOT; + ent->s.modelindex = gi.modelindex ("models/objects/banner/tris.md2"); + ent->s.frame = rand() % 16; + gi.linkentity (ent); + + ent->think = misc_banner_think; + ent->nextthink = level.time + FRAMETIME; +} + +/*QUAKED misc_deadsoldier (1 .5 0) (-16 -16 0) (16 16 16) ON_BACK ON_STOMACH BACK_DECAP FETAL_POS SIT_DECAP IMPALED +This is the dead player model. Comes in 6 exciting different poses! +*/ +void misc_deadsoldier_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + int n; + + if (self->health > -80) + return; + + gi.sound (self, CHAN_BODY, gi.soundindex ("misc/udeath.wav"), 1, ATTN_NORM, 0); + for (n= 0; n < 4; n++) + ThrowGib (self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC); + ThrowHead (self, "models/objects/gibs/head2/tris.md2", damage, GIB_ORGANIC); +} + +void SP_misc_deadsoldier (edict_t *ent) +{ + if (deathmatch->value) + { // auto-remove for deathmatch + G_FreeEdict (ent); + return; + } + + ent->movetype = MOVETYPE_NONE; + ent->solid = SOLID_BBOX; + ent->s.modelindex=gi.modelindex ("models/deadbods/dude/tris.md2"); + + // Defaults to frame 0 + if (ent->spawnflags & 2) + ent->s.frame = 1; + else if (ent->spawnflags & 4) + ent->s.frame = 2; + else if (ent->spawnflags & 8) + ent->s.frame = 3; + else if (ent->spawnflags & 16) + ent->s.frame = 4; + else if (ent->spawnflags & 32) + ent->s.frame = 5; + else + ent->s.frame = 0; + + VectorSet (ent->mins, -16, -16, 0); + VectorSet (ent->maxs, 16, 16, 16); + ent->deadflag = DEAD_DEAD; + ent->takedamage = DAMAGE_YES; + ent->svflags |= SVF_MONSTER|SVF_DEADMONSTER; + ent->die = misc_deadsoldier_die; + ent->monsterinfo.aiflags |= AI_GOOD_GUY; + + gi.linkentity (ent); +} + +/*QUAKED misc_viper (1 .5 0) (-16 -16 0) (16 16 32) +This is the Viper for the flyby bombing. +It is trigger_spawned, so you must have something use it for it to show up. +There must be a path for it to follow once it is activated. + +"speed" How fast the Viper should fly +*/ + +extern void train_use (edict_t *self, edict_t *other, edict_t *activator); +extern void func_train_find (edict_t *self); + +void misc_viper_use (edict_t *self, edict_t *other, edict_t *activator) +{ + self->svflags &= ~SVF_NOCLIENT; + self->use = train_use; + train_use (self, other, activator); +} + +void SP_misc_viper (edict_t *ent) +{ + if (!ent->target) + { + gi.dprintf ("misc_viper without a target at %s\n", vtos(ent->absmin)); + G_FreeEdict (ent); + return; + } + + if (!ent->speed) + ent->speed = 300; + + ent->movetype = MOVETYPE_PUSH; + ent->solid = SOLID_NOT; + ent->s.modelindex = gi.modelindex ("models/ships/viper/tris.md2"); + VectorSet (ent->mins, -16, -16, 0); + VectorSet (ent->maxs, 16, 16, 32); + + ent->think = func_train_find; + ent->nextthink = level.time + FRAMETIME; + ent->use = misc_viper_use; + ent->svflags |= SVF_NOCLIENT; + ent->moveinfo.accel = ent->moveinfo.decel = ent->moveinfo.speed = ent->speed; + + gi.linkentity (ent); +} + + +/*QUAKED misc_bigviper (1 .5 0) (-176 -120 -24) (176 120 72) +This is a large stationary viper as seen in Paul's intro +*/ +void SP_misc_bigviper (edict_t *ent) +{ + ent->movetype = MOVETYPE_NONE; + ent->solid = SOLID_BBOX; + VectorSet (ent->mins, -176, -120, -24); + VectorSet (ent->maxs, 176, 120, 72); + ent->s.modelindex = gi.modelindex ("models/ships/bigviper/tris.md2"); + gi.linkentity (ent); +} + + +/*QUAKED misc_viper_bomb (1 0 0) (-8 -8 -8) (8 8 8) +"dmg" how much boom should the bomb make? +*/ +void misc_viper_bomb_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + G_UseTargets (self, self->activator); + + self->s.origin[2] = self->absmin[2] + 1; + T_RadiusDamage (self, self, self->dmg, NULL, self->dmg+40, MOD_BOMB); + BecomeExplosion2 (self); +} + +void misc_viper_bomb_prethink (edict_t *self) +{ + vec3_t v; + float diff; + + self->groundentity = NULL; + + diff = self->timestamp - level.time; + if (diff < -1.0) + diff = -1.0; + + VectorScale (self->moveinfo.dir, 1.0 + diff, v); + v[2] = diff; + + diff = self->s.angles[2]; + vectoangles (v, self->s.angles); + self->s.angles[2] = diff + 10; +} + +void misc_viper_bomb_use (edict_t *self, edict_t *other, edict_t *activator) +{ + edict_t *viper; + + self->solid = SOLID_BBOX; + self->svflags &= ~SVF_NOCLIENT; + self->s.effects |= EF_ROCKET; + self->use = NULL; + self->movetype = MOVETYPE_TOSS; + self->prethink = misc_viper_bomb_prethink; + self->touch = misc_viper_bomb_touch; + self->activator = activator; + + viper = G_Find (NULL, FOFS(classname), "misc_viper"); + VectorScale (viper->moveinfo.dir, viper->moveinfo.speed, self->velocity); + + self->timestamp = level.time; + VectorCopy (viper->moveinfo.dir, self->moveinfo.dir); +} + +void SP_misc_viper_bomb (edict_t *self) +{ + self->movetype = MOVETYPE_NONE; + self->solid = SOLID_NOT; + VectorSet (self->mins, -8, -8, -8); + VectorSet (self->maxs, 8, 8, 8); + + self->s.modelindex = gi.modelindex ("models/objects/bomb/tris.md2"); + + if (!self->dmg) + self->dmg = 1000; + + self->use = misc_viper_bomb_use; + self->svflags |= SVF_NOCLIENT; + + gi.linkentity (self); +} + + +/*QUAKED misc_strogg_ship (1 .5 0) (-16 -16 0) (16 16 32) +This is a Storgg ship for the flybys. +It is trigger_spawned, so you must have something use it for it to show up. +There must be a path for it to follow once it is activated. + +"speed" How fast it should fly +*/ + +extern void train_use (edict_t *self, edict_t *other, edict_t *activator); +extern void func_train_find (edict_t *self); + +void misc_strogg_ship_use (edict_t *self, edict_t *other, edict_t *activator) +{ + self->svflags &= ~SVF_NOCLIENT; + self->use = train_use; + train_use (self, other, activator); +} + +void SP_misc_strogg_ship (edict_t *ent) +{ + if (!ent->target) + { + gi.dprintf ("%s without a target at %s\n", ent->classname, vtos(ent->absmin)); + G_FreeEdict (ent); + return; + } + + if (!ent->speed) + ent->speed = 300; + + ent->movetype = MOVETYPE_PUSH; + ent->solid = SOLID_NOT; + ent->s.modelindex = gi.modelindex ("models/ships/strogg1/tris.md2"); + VectorSet (ent->mins, -16, -16, 0); + VectorSet (ent->maxs, 16, 16, 32); + + ent->think = func_train_find; + ent->nextthink = level.time + FRAMETIME; + ent->use = misc_strogg_ship_use; + ent->svflags |= SVF_NOCLIENT; + ent->moveinfo.accel = ent->moveinfo.decel = ent->moveinfo.speed = ent->speed; + + gi.linkentity (ent); +} + +// RAFAEL 17-APR-98 +/*QUAKED misc_transport (1 0 0) (-8 -8 -8) (8 8 8) TRIGGER_SPAWN +Maxx's transport at end of game +*/ +void SP_misc_transport (edict_t *ent) +{ + + if (!ent->target) + { + gi.dprintf ("%s without a target at %s\n", ent->classname, vtos(ent->absmin)); + G_FreeEdict (ent); + return; + } + + if (!ent->speed) + ent->speed = 300; + + ent->movetype = MOVETYPE_PUSH; + ent->solid = SOLID_NOT; + ent->s.modelindex = gi.modelindex ("models/objects/ship/tris.md2"); + + VectorSet (ent->mins, -16, -16, 0); + VectorSet (ent->maxs, 16, 16, 32); + + ent->think = func_train_find; + ent->nextthink = level.time + FRAMETIME; + ent->use = misc_strogg_ship_use; + ent->svflags |= SVF_NOCLIENT; + ent->moveinfo.accel = ent->moveinfo.decel = ent->moveinfo.speed = ent->speed; + + if (!(ent->spawnflags & 1)) + { + ent->spawnflags |= 1; + } + + gi.linkentity (ent); +} +// END 17-APR-98 + +/*QUAKED misc_satellite_dish (1 .5 0) (-64 -64 0) (64 64 128) +*/ +void misc_satellite_dish_think (edict_t *self) +{ + self->s.frame++; + if (self->s.frame < 38) + self->nextthink = level.time + FRAMETIME; +} + +void misc_satellite_dish_use (edict_t *self, edict_t *other, edict_t *activator) +{ + self->s.frame = 0; + self->think = misc_satellite_dish_think; + self->nextthink = level.time + FRAMETIME; +} + +void SP_misc_satellite_dish (edict_t *ent) +{ + ent->movetype = MOVETYPE_NONE; + ent->solid = SOLID_BBOX; + VectorSet (ent->mins, -64, -64, 0); + VectorSet (ent->maxs, 64, 64, 128); + ent->s.modelindex = gi.modelindex ("models/objects/satellite/tris.md2"); + ent->use = misc_satellite_dish_use; + gi.linkentity (ent); +} + + +/*QUAKED light_mine1 (0 1 0) (-2 -2 -12) (2 2 12) +*/ +void SP_light_mine1 (edict_t *ent) +{ + ent->movetype = MOVETYPE_NONE; + ent->solid = SOLID_BBOX; + ent->s.modelindex = gi.modelindex ("models/objects/minelite/light1/tris.md2"); + gi.linkentity (ent); +} + + +/*QUAKED light_mine2 (0 1 0) (-2 -2 -12) (2 2 12) +*/ +void SP_light_mine2 (edict_t *ent) +{ + ent->movetype = MOVETYPE_NONE; + ent->solid = SOLID_BBOX; + ent->s.modelindex = gi.modelindex ("models/objects/minelite/light2/tris.md2"); + gi.linkentity (ent); +} + + +/*QUAKED misc_gib_arm (1 0 0) (-8 -8 -8) (8 8 8) +Intended for use with the target_spawner +*/ +void SP_misc_gib_arm (edict_t *ent) +{ + gi.setmodel (ent, "models/objects/gibs/arm/tris.md2"); + ent->solid = SOLID_NOT; + ent->s.effects |= EF_GIB; + ent->takedamage = DAMAGE_YES; + ent->die = gib_die; + ent->movetype = MOVETYPE_TOSS; + ent->svflags |= SVF_MONSTER; + ent->deadflag = DEAD_DEAD; + ent->avelocity[0] = random()*200; + ent->avelocity[1] = random()*200; + ent->avelocity[2] = random()*200; + ent->think = G_FreeEdict; + ent->nextthink = level.time + 30; + gi.linkentity (ent); +} + +/*QUAKED misc_gib_leg (1 0 0) (-8 -8 -8) (8 8 8) +Intended for use with the target_spawner +*/ +void SP_misc_gib_leg (edict_t *ent) +{ + gi.setmodel (ent, "models/objects/gibs/leg/tris.md2"); + ent->solid = SOLID_NOT; + ent->s.effects |= EF_GIB; + ent->takedamage = DAMAGE_YES; + ent->die = gib_die; + ent->movetype = MOVETYPE_TOSS; + ent->svflags |= SVF_MONSTER; + ent->deadflag = DEAD_DEAD; + ent->avelocity[0] = random()*200; + ent->avelocity[1] = random()*200; + ent->avelocity[2] = random()*200; + ent->think = G_FreeEdict; + ent->nextthink = level.time + 30; + gi.linkentity (ent); +} + +/*QUAKED misc_gib_head (1 0 0) (-8 -8 -8) (8 8 8) +Intended for use with the target_spawner +*/ +void SP_misc_gib_head (edict_t *ent) +{ + gi.setmodel (ent, "models/objects/gibs/head/tris.md2"); + ent->solid = SOLID_NOT; + ent->s.effects |= EF_GIB; + ent->takedamage = DAMAGE_YES; + ent->die = gib_die; + ent->movetype = MOVETYPE_TOSS; + ent->svflags |= SVF_MONSTER; + ent->deadflag = DEAD_DEAD; + ent->avelocity[0] = random()*200; + ent->avelocity[1] = random()*200; + ent->avelocity[2] = random()*200; + ent->think = G_FreeEdict; + ent->nextthink = level.time + 30; + gi.linkentity (ent); +} + +//===================================================== + +/*QUAKED target_character (0 0 1) ? +used with target_string (must be on same "team") +"count" is position in the string (starts at 1) +*/ + +void SP_target_character (edict_t *self) +{ + self->movetype = MOVETYPE_PUSH; + gi.setmodel (self, self->model); + self->solid = SOLID_BSP; + self->s.frame = 12; + gi.linkentity (self); + return; +} + + +/*QUAKED target_string (0 0 1) (-8 -8 -8) (8 8 8) +*/ + +void target_string_use (edict_t *self, edict_t *other, edict_t *activator) +{ + edict_t *e; + int n, l; + char c; + + l = strlen(self->message); + for (e = self->teammaster; e; e = e->teamchain) + { + if (!e->count) + continue; + n = e->count - 1; + if (n > l) + { + e->s.frame = 12; + continue; + } + + c = self->message[n]; + if (c >= '0' && c <= '9') + e->s.frame = c - '0'; + else if (c == '-') + e->s.frame = 10; + else if (c == ':') + e->s.frame = 11; + else + e->s.frame = 12; + } +} + +void SP_target_string (edict_t *self) +{ + if (!self->message) + self->message = ""; + self->use = target_string_use; +} + + +/*QUAKED func_clock (0 0 1) (-8 -8 -8) (8 8 8) TIMER_UP TIMER_DOWN START_OFF MULTI_USE +target a target_string with this + +The default is to be a time of day clock + +TIMER_UP and TIMER_DOWN run for "count" seconds and the fire "pathtarget" +If START_OFF, this entity must be used before it starts + +"style" 0 "xx" + 1 "xx:xx" + 2 "xx:xx:xx" +*/ + +#define CLOCK_MESSAGE_SIZE 16 + +// don't let field width of any clock messages change, or it +// could cause an overwrite after a game load + +static void func_clock_reset (edict_t *self) +{ + self->activator = NULL; + if (self->spawnflags & 1) + { + self->health = 0; + self->wait = self->count; + } + else if (self->spawnflags & 2) + { + self->health = self->count; + self->wait = 0; + } +} + +static void func_clock_format_countdown (edict_t *self) +{ + if (self->style == 0) + { + Com_sprintf (self->message, CLOCK_MESSAGE_SIZE, "%2i", self->health); + return; + } + + if (self->style == 1) + { + Com_sprintf(self->message, CLOCK_MESSAGE_SIZE, "%2i:%2i", self->health / 60, self->health % 60); + if (self->message[3] == ' ') + self->message[3] = '0'; + return; + } + + if (self->style == 2) + { + Com_sprintf(self->message, CLOCK_MESSAGE_SIZE, "%2i:%2i:%2i", self->health / 3600, (self->health - (self->health / 3600) * 3600) / 60, self->health % 60); + if (self->message[3] == ' ') + self->message[3] = '0'; + if (self->message[6] == ' ') + self->message[6] = '0'; + return; + } +} + +void func_clock_think (edict_t *self) +{ + if (!self->enemy) + { + self->enemy = G_Find (NULL, FOFS(targetname), self->target); + if (!self->enemy) + return; + } + + if (self->spawnflags & 1) + { + func_clock_format_countdown (self); + self->health++; + } + else if (self->spawnflags & 2) + { + func_clock_format_countdown (self); + self->health--; + } + else + { + struct tm *ltime; + time_t gmtime; + + time(&gmtime); + ltime = localtime(&gmtime); + Com_sprintf (self->message, CLOCK_MESSAGE_SIZE, "%2i:%2i:%2i", ltime->tm_hour, ltime->tm_min, ltime->tm_sec); + if (self->message[3] == ' ') + self->message[3] = '0'; + if (self->message[6] == ' ') + self->message[6] = '0'; + } + + self->enemy->message = self->message; + self->enemy->use (self->enemy, self, self); + + if (((self->spawnflags & 1) && (self->health > self->wait)) || + ((self->spawnflags & 2) && (self->health < self->wait))) + { + if (self->pathtarget) + { + char *savetarget; + char *savemessage; + + savetarget = self->target; + savemessage = self->message; + self->target = self->pathtarget; + self->message = NULL; + G_UseTargets (self, self->activator); + self->target = savetarget; + self->message = savemessage; + } + + if (!(self->spawnflags & 8)) + return; + + func_clock_reset (self); + + if (self->spawnflags & 4) + return; + } + + self->nextthink = level.time + 1; +} + +void func_clock_use (edict_t *self, edict_t *other, edict_t *activator) +{ + if (!(self->spawnflags & 8)) + self->use = NULL; + if (self->activator) + return; + self->activator = activator; + self->think (self); +} + +void SP_func_clock (edict_t *self) +{ + if (!self->target) + { + gi.dprintf("%s with no target at %s\n", self->classname, vtos(self->s.origin)); + G_FreeEdict (self); + return; + } + + if ((self->spawnflags & 2) && (!self->count)) + { + gi.dprintf("%s with no count at %s\n", self->classname, vtos(self->s.origin)); + G_FreeEdict (self); + return; + } + + if ((self->spawnflags & 1) && (!self->count)) + self->count = 60*60;; + + func_clock_reset (self); + + self->message = gi.TagMalloc (CLOCK_MESSAGE_SIZE, TAG_LEVEL); + + self->think = func_clock_think; + + if (self->spawnflags & 4) + self->use = func_clock_use; + else + self->nextthink = level.time + 1; +} + +//================================================================================= + +void teleporter_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + edict_t *dest; + int i; + + if (!other->client) + return; + dest = G_Find (NULL, FOFS(targetname), self->target); + if (!dest) + { + gi.dprintf ("Couldn't find destination\n"); + return; + } + + + //route update + if(chedit->value && CurrentIndex < MAXNODES && other == &g_edicts[1]) + { + gi.bprintf(PRINT_HIGH,"teleport!\n"); + VectorCopy(self->s.origin,Route[CurrentIndex].Pt); + Route[CurrentIndex].ent = NULL; + Route[CurrentIndex].state = GRS_TELEPORT; + if(++CurrentIndex < MAXNODES) + { + memset(&Route[CurrentIndex],0,sizeof(route_t)); + Route[CurrentIndex].index = Route[CurrentIndex - 1].index +1; + } + } + + if(other->svflags & SVF_MONSTER) + { + if(other->client->zc.route_trace && other->client->zc.routeindex < CurrentIndex ) + { + if(Route[other->client->zc.routeindex].state == GRS_TELEPORT + /*&& Route[other->client->zc.routeindex].ent == self*/) + { + other->client->zc.routeindex++; +//gi.bprintf(PRINT_HIGH,"teleport!\n"); + + } + + if(other->client->zc.routeindex < CurrentIndex) + { + if(Route[other->client->zc.routeindex].state == GRS_GRAPRELEASE) + { + other->client->zc.routeindex++; +//gi.bprintf(PRINT_HIGH,"Groff!\n"); + } + } + } + } + +//ZOID + CTFPlayerResetGrapple(other); +//ZOID + + // unlink to make sure it can't possibly interfere with KillBox + gi.unlinkentity (other); + + VectorCopy (dest->s.origin, other->s.origin); + VectorCopy (dest->s.origin, other->s.old_origin); + other->s.origin[2] += 10; + + // clear the velocity and hold them in place briefly + VectorClear (other->velocity); + other->client->ps.pmove.pm_time = 160>>3; // hold time + other->client->ps.pmove.pm_flags |= PMF_TIME_TELEPORT; + + // draw the teleport splash at source and on the player + self->owner->s.event = EV_PLAYER_TELEPORT; + other->s.event = EV_PLAYER_TELEPORT; + + // set angles + for (i=0 ; i<3 ; i++) + other->client->ps.pmove.delta_angles[i] = ANGLE2SHORT(dest->s.angles[i] - other->client->resp.cmd_angles[i]); + + VectorClear (other->s.angles); + VectorClear (other->client->ps.viewangles); + VectorClear (other->client->v_angle); + + // kill anything at the destination + KillBox (other); + + gi.linkentity (other); +} + +/*QUAKED misc_teleporter (1 0 0) (-32 -32 -24) (32 32 -16) +Stepping onto this disc will teleport players to the targeted misc_teleporter_dest object. +*/ +void SP_misc_teleporter (edict_t *ent) +{ + edict_t *trig; + + if (!ent->target) + { + gi.dprintf ("teleporter without a target.\n"); + G_FreeEdict (ent); + return; + } + + gi.setmodel (ent, "models/objects/dmspot/tris.md2"); + ent->s.skinnum = 1; + ent->s.effects = EF_TELEPORTER; + ent->s.sound = gi.soundindex ("world/amb10.wav"); + ent->solid = SOLID_BBOX; + + VectorSet (ent->mins, -32, -32, -24); + VectorSet (ent->maxs, 32, 32, -16); + gi.linkentity (ent); + + trig = G_Spawn (); + trig->touch = teleporter_touch; + trig->solid = SOLID_TRIGGER; + trig->target = ent->target; + trig->owner = ent; + VectorCopy (ent->s.origin, trig->s.origin); + VectorSet (trig->mins, -8, -8, 8); + VectorSet (trig->maxs, 8, 8, 24); + gi.linkentity (trig); + +} + +/*QUAKED misc_teleporter_dest (1 0 0) (-32 -32 -24) (32 32 -16) +Point teleporters at these. +*/ +void SP_misc_teleporter_dest (edict_t *ent) +{ + gi.setmodel (ent, "models/objects/dmspot/tris.md2"); + ent->s.skinnum = 0; + ent->solid = SOLID_BBOX; +// ent->s.effects |= EF_FLIES; + VectorSet (ent->mins, -32, -32, -24); + VectorSet (ent->maxs, 32, 32, -16); + gi.linkentity (ent); +} + +/*QUAKED misc_amb4 (1 0 0) (-16 -16 -16) (16 16 16) +Mal's amb4 loop entity +*/ +static int amb4sound; + +void amb4_think (edict_t *ent) +{ + ent->nextthink = level.time + 2.7; + gi.sound(ent, CHAN_VOICE, amb4sound, 1, ATTN_NONE, 0); +} + +void SP_misc_amb4 (edict_t *ent) +{ + ent->think = amb4_think; + ent->nextthink = level.time + 1; + amb4sound = gi.soundindex ("world/amb4.wav"); + gi.linkentity (ent); +} + +/*QUAKED misc_nuke (1 0 0) (-16 -16 -16) (16 16 16) +*/ + +void use_nuke (edict_t *self, edict_t *other, edict_t *activator) +{ + edict_t *from = g_edicts; + + for ( ; from < &g_edicts[globals.num_edicts]; from++) + { + if (from == self) + continue; + if (from->client) + { + T_Damage (from, self, self, vec3_origin, from->s.origin, vec3_origin, 100000, 1, 0, MOD_TRAP); + } + else if (from->svflags & SVF_MONSTER) + { + G_FreeEdict (from); + } + } + + self->use = NULL; +} + +void SP_misc_nuke (edict_t *ent) +{ + ent->use = use_nuke; +} \ No newline at end of file diff --git a/src/g_monster.c b/src/g_monster.c new file mode 100644 index 0000000..71cb6f9 --- /dev/null +++ b/src/g_monster.c @@ -0,0 +1,795 @@ +#include "g_local.h" +#include "bot.h" + +// +// monster weapons +// + +//FIXME mosnters should call these with a totally accurate direction +// and we can mess it up based on skill. Spread should be for normal +// and we can tighten or loosen based on skill. We could muck with +// the damages too, but I'm not sure that's such a good idea. +/*void monster_fire_bullet (edict_t *self, vec3_t start, vec3_t dir, int damage, int kick, int hspread, int vspread, int flashtype) +{ + fire_bullet (self, start, dir, damage, kick, hspread, vspread, MOD_UNKNOWN); + + gi.WriteByte (svc_muzzleflash2); + gi.WriteShort (self - g_edicts); + gi.WriteByte (flashtype); + gi.multicast (start, MULTICAST_PVS); +} + +void monster_fire_shotgun (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick, int hspread, int vspread, int count, int flashtype) +{ + fire_shotgun (self, start, aimdir, damage, kick, hspread, vspread, count, MOD_UNKNOWN); + + gi.WriteByte (svc_muzzleflash2); + gi.WriteShort (self - g_edicts); + gi.WriteByte (flashtype); + gi.multicast (start, MULTICAST_PVS); +} + +void monster_fire_blaster (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int flashtype, int effect) +{ + fire_blaster (self, start, dir, damage, speed, effect, false); + + gi.WriteByte (svc_muzzleflash2); + gi.WriteShort (self - g_edicts); + gi.WriteByte (flashtype); + gi.multicast (start, MULTICAST_PVS); +} + +void monster_fire_grenade (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, int flashtype) +{ + fire_grenade (self, start, aimdir, damage, speed, 2.5, damage+40); + + gi.WriteByte (svc_muzzleflash2); + gi.WriteShort (self - g_edicts); + gi.WriteByte (flashtype); + gi.multicast (start, MULTICAST_PVS); +} + +void monster_fire_rocket (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int flashtype) +{ + fire_rocket (self, start, dir, damage, speed, damage+20, damage); + + gi.WriteByte (svc_muzzleflash2); + gi.WriteShort (self - g_edicts); + gi.WriteByte (flashtype); + gi.multicast (start, MULTICAST_PVS); +} + +void monster_fire_railgun (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick, int flashtype) +{ + fire_rail (self, start, aimdir, damage, kick); + + gi.WriteByte (svc_muzzleflash2); + gi.WriteShort (self - g_edicts); + gi.WriteByte (flashtype); + gi.multicast (start, MULTICAST_PVS); +} + +void monster_fire_bfg (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, int kick, float damage_radius, int flashtype) +{ + fire_bfg (self, start, aimdir, damage, speed, damage_radius); + + gi.WriteByte (svc_muzzleflash2); + gi.WriteShort (self - g_edicts); + gi.WriteByte (flashtype); + gi.multicast (start, MULTICAST_PVS); +} +*/ + + +// +// Monster utility functions +// +/* +static void M_FliesOff (edict_t *self) +{ + self->s.effects &= ~EF_FLIES; + self->s.sound = 0; +} + +static void M_FliesOn (edict_t *self) +{ + self->s.effects |= EF_FLIES; + self->s.sound = gi.soundindex ("infantry/inflies1.wav"); + self->think = M_FliesOff; + self->nextthink = level.time + 60; +} + +void M_FlyCheck (edict_t *self) +{ + if (self->waterlevel) + return; + + if (random() > 0.5) + return; + + self->think = M_FliesOn; + self->nextthink = level.time + 5 + 10 * random(); +} + +void AttackFinished (edict_t *self, float time) +{ + self->monsterinfo.attack_finished = level.time + time; +} +*/ +/* +void M_CheckGround (edict_t *ent) +{ + vec3_t point; + trace_t trace; + + if (ent->flags & (FL_SWIM|FL_FLY)) + return; + + if (ent->velocity[2] > 100) + { + ent->groundentity = NULL; + return; + } + +// if the hull point one-quarter unit down is solid the entity is on ground + point[0] = ent->s.origin[0]; + point[1] = ent->s.origin[1]; + point[2] = ent->s.origin[2] - 0.25; + + if(!deathmatch->value) trace = gi.trace (ent->s.origin, ent->mins, ent->maxs, point, ent, MASK_MONSTERSOLID); + else trace = gi.trace (ent->s.origin, ent->mins, ent->maxs, point, ent, MASK_PLAYERSOLID); + + // check steepness + if ( trace.plane.normal[2] < 0.7 && !trace.startsolid) + { + ent->groundentity = NULL; + return; + } + + ent->groundentity = trace.ent; + ent->groundentity_linkcount = trace.ent->linkcount; + if(ent->client) ent->client->zc.ground_contents = trace.contents; +// if (!trace.startsolid && !trace.allsolid) +// VectorCopy (trace.endpos, ent->s.origin); + if (!trace.startsolid && !trace.allsolid) + { + VectorCopy (trace.endpos, ent->s.origin); + ent->groundentity = trace.ent; + ent->groundentity_linkcount = trace.ent->linkcount; + ent->velocity[2] = 0; + return; + } + +} +*/ + +void M_CheckGround (edict_t *ent) +{ + vec3_t point,stp,v1,v2; + trace_t trace,tracep; + + if (ent->flags & (FL_SWIM|FL_FLY)) + return; + + if(ent->client) + { + ent->client->zc.ground_slope = 1.0; + +/* if( ent->client->ctf_grapple && ent->client->ctf_grapplestate == CTF_GRAPPLE_STATE_PULL) + { + if(ent->velocity[2] > 0) + { + ent->groundentity = NULL; + return; + } + }*/ + } + //ent->groundentity = NULL; + if (ent->velocity[2] > 100) + { + ent->groundentity = NULL; +//gi.bprintf(PRINT_HIGH,"ogeeX\n"); + return; + } + +// if the hull point one-quarter unit down is solid the entity is on ground + point[0] = ent->s.origin[0]; + point[1] = ent->s.origin[1]; + point[2] = ent->s.origin[2] - 0.25; + + trace = gi.trace (ent->s.origin, ent->mins, ent->maxs, point, ent,MASK_BOTSOLID/*CONTENTS_SOLID|CONTENTS_WINDOW|CONTENTS_MONSTER*/); + //MASK_BOTSOLID /*MASK_PLAYERSOLID*/); + + // check steepness + if ( trace.fraction == 1.0/*trace.plane.normal[2] < 0.7*/ + && (!trace.startsolid && !trace.allsolid)) + { + ent->groundentity = NULL; +// ent->groundentity_linkcount = trace.ent->linkcount; +//gi.bprintf(PRINT_HIGH,"NULLKUN\n"); + return; + } + + if(/*trace.ent &&*/ (/*trace.startsolid ||*/ trace.allsolid)) + { + if(1/*trace.ent->classname[0] == 'f' && trace.ent->classname[5] == 'r'*/) + { + VectorSet(v1,-16,-16,-24); + VectorSet(v2,16,16,4); + + VectorCopy(ent->s.origin,stp); +// gi.bprintf(PRINT_HIGH,"ogeeY\n"); + stp[2] += 24; + tracep = gi.trace (stp, v1, v2, point, ent, MASK_BOTSOLID /*MASK_PLAYERSOLID*/); + if(tracep.ent && !tracep.allsolid /*&& !tracep.startsolid*/) + { + if (tracep.ent->classname[0] == 'f' /*&& tracep.ent->classname[5] == 'r'*/) + { + VectorCopy(tracep.endpos,ent->s.origin); +// gi.bprintf(PRINT_HIGH,"ogee done\n"); + ent->groundentity = tracep.ent; + /*if(tracep.ent->classname[5] == 'r')*/ ent->groundentity_linkcount = tracep.ent->linkcount; + //ent->velocity[2] = 0; + gi.linkentity(ent); + return; + } + } + } + } + +// ent->groundentity = trace.ent; +// ent->groundentity_linkcount = trace.ent->linkcount; +// if (!trace.startsolid && !trace.allsolid) +// VectorCopy (trace.endpos, ent->s.origin); + + if (/*!trace.startsolid &&*/ !trace.allsolid) + { + if(ent->client) + { + ent->client->zc.ground_contents = trace.contents; + ent->client->zc.ground_slope = trace.plane.normal[2]; + } + VectorCopy (trace.endpos, ent->s.origin); + ent->groundentity = trace.ent; + ent->groundentity_linkcount = trace.ent->linkcount; + // ent->velocity[2] = 0; +// VectorCopy(trace.endpos,ent->s.origin); + } +// else gi.bprintf(PRINT_HIGH,"mopmop! %x %f\n",trace.contents,ent->velocity[2]); + gi.linkentity(ent); +} + + +void M_CatagorizePosition (edict_t *ent) +{ + vec3_t point; + int cont; + +// +// get waterlevel +// + point[0] = ent->s.origin[0]; + point[1] = ent->s.origin[1]; + point[2] = ent->s.origin[2] + ent->mins[2] + 1; + cont = gi.pointcontents (point); + + if (!(cont & MASK_WATER)) + { + ent->waterlevel = 0; + ent->watertype = 0; + return; + } + + ent->watertype = cont; + ent->waterlevel = 1; + point[2] += 26; + cont = gi.pointcontents (point); + if (!(cont & MASK_WATER)) + return; + + ent->waterlevel = 2; + point[2] += 22; + cont = gi.pointcontents (point); + if (cont & MASK_WATER) + ent->waterlevel = 3; +} + +/* +void M_WorldEffects (edict_t *ent) +{ + int dmg; + + if (ent->health > 0) + { + if (!(ent->flags & FL_SWIM)) + { + if (ent->waterlevel < 3) + { + ent->air_finished = level.time + 12; + } + else if (ent->air_finished < level.time) + { // drown! + if (ent->pain_debounce_time < level.time) + { + dmg = 2 + 2 * floor(level.time - ent->air_finished); + if (dmg > 15) + dmg = 15; + T_Damage (ent, world, world, vec3_origin, ent->s.origin, vec3_origin, dmg, 0, DAMAGE_NO_ARMOR, MOD_WATER); + ent->pain_debounce_time = level.time + 1; + } + } + } + else + { + if (ent->waterlevel > 0) + { + ent->air_finished = level.time + 9; + } + else if (ent->air_finished < level.time) + { // suffocate! + if (ent->pain_debounce_time < level.time) + { + dmg = 2 + 2 * floor(level.time - ent->air_finished); + if (dmg > 15) + dmg = 15; + T_Damage (ent, world, world, vec3_origin, ent->s.origin, vec3_origin, dmg, 0, DAMAGE_NO_ARMOR, MOD_WATER); + ent->pain_debounce_time = level.time + 1; + } + } + } + } + + if (ent->waterlevel == 0) + { + if (ent->flags & FL_INWATER) + { + gi.sound (ent, CHAN_BODY, gi.soundindex("player/watr_out.wav"), 1, ATTN_NORM, 0); + ent->flags &= ~FL_INWATER; + } + return; + } + + if ((ent->watertype & CONTENTS_LAVA) && !(ent->flags & FL_IMMUNE_LAVA)) + { + if (ent->damage_debounce_time < level.time) + { + ent->damage_debounce_time = level.time + 0.2; + T_Damage (ent, world, world, vec3_origin, ent->s.origin, vec3_origin, 10*ent->waterlevel, 0, 0, MOD_LAVA); + } + } + if ((ent->watertype & CONTENTS_SLIME) && !(ent->flags & FL_IMMUNE_SLIME)) + { + if (ent->damage_debounce_time < level.time) + { + ent->damage_debounce_time = level.time + 1; + T_Damage (ent, world, world, vec3_origin, ent->s.origin, vec3_origin, 4*ent->waterlevel, 0, 0, MOD_SLIME); + } + } + + if ( !(ent->flags & FL_INWATER) ) + { + if (ent->watertype & CONTENTS_LAVA) + if (random() <= 0.5) + gi.sound (ent, CHAN_BODY, gi.soundindex("player/lava1.wav"), 1, ATTN_NORM, 0); + else + gi.sound (ent, CHAN_BODY, gi.soundindex("player/lava2.wav"), 1, ATTN_NORM, 0); + else if (ent->watertype & CONTENTS_SLIME) + gi.sound (ent, CHAN_BODY, gi.soundindex("player/watr_in.wav"), 1, ATTN_NORM, 0); + else if (ent->watertype & CONTENTS_WATER) + gi.sound (ent, CHAN_BODY, gi.soundindex("player/watr_in.wav"), 1, ATTN_NORM, 0); + + ent->flags |= FL_INWATER; + ent->damage_debounce_time = 0; + } +} +*/ + +void M_droptofloor (edict_t *ent) +{ + vec3_t end; + trace_t trace; + + ent->s.origin[2] += 1; + VectorCopy (ent->s.origin, end); + end[2] -= 256; + + trace = gi.trace (ent->s.origin, ent->mins, ent->maxs, end, ent, MASK_MONSTERSOLID); + + if (trace.fraction == 1 || trace.allsolid) + return; + + VectorCopy (trace.endpos, ent->s.origin); + + gi.linkentity (ent); + M_CheckGround (ent); + M_CatagorizePosition (ent); +} + +/* +void M_SetEffects (edict_t *ent) +{ + ent->s.effects &= ~(EF_COLOR_SHELL|EF_POWERSCREEN); + ent->s.renderfx &= ~(RF_SHELL_RED|RF_SHELL_GREEN|RF_SHELL_BLUE); + + if (ent->monsterinfo.aiflags & AI_RESURRECTING) + { + ent->s.effects |= EF_COLOR_SHELL; + ent->s.renderfx |= RF_SHELL_RED; + } + + if (ent->health <= 0) + return; + + if (ent->powerarmor_time > level.time) + { + if (ent->monsterinfo.power_armor_type == POWER_ARMOR_SCREEN) + { + ent->s.effects |= EF_POWERSCREEN; + } + else if (ent->monsterinfo.power_armor_type == POWER_ARMOR_SHIELD) + { + ent->s.effects |= EF_COLOR_SHELL; + ent->s.renderfx |= RF_SHELL_GREEN; + } + } +} + + +void M_MoveFrame (edict_t *self) +{ + mmove_t *move; + int index; + + move = self->monsterinfo.currentmove; + self->nextthink = level.time + FRAMETIME; + + if ((self->monsterinfo.nextframe) && (self->monsterinfo.nextframe >= move->firstframe) && (self->monsterinfo.nextframe <= move->lastframe)) + { + self->s.frame = self->monsterinfo.nextframe; + self->monsterinfo.nextframe = 0; + } + else + { + if (self->s.frame == move->lastframe) + { + if (move->endfunc) + { + move->endfunc (self); + + // regrab move, endfunc is very likely to change it + move = self->monsterinfo.currentmove; + + // check for death + if (self->svflags & SVF_DEADMONSTER) + return; + } + } + + if (self->s.frame < move->firstframe || self->s.frame > move->lastframe) + { + self->monsterinfo.aiflags &= ~AI_HOLD_FRAME; + self->s.frame = move->firstframe; + } + else + { + if (!(self->monsterinfo.aiflags & AI_HOLD_FRAME)) + { + self->s.frame++; + if (self->s.frame > move->lastframe) + self->s.frame = move->firstframe; + } + } + } + + index = self->s.frame - move->firstframe; + if (move->frame[index].aifunc) + if (!(self->monsterinfo.aiflags & AI_HOLD_FRAME)) + move->frame[index].aifunc (self, move->frame[index].dist * self->monsterinfo.scale); + else + move->frame[index].aifunc (self, 0); + + if (move->frame[index].thinkfunc) + move->frame[index].thinkfunc (self); +} + + +void monster_think (edict_t *self) +{ + M_MoveFrame (self); + if (self->linkcount != self->monsterinfo.linkcount) + { + self->monsterinfo.linkcount = self->linkcount; + M_CheckGround (self); + } + M_CatagorizePosition (self); + M_WorldEffects (self); + M_SetEffects (self); +} + + + +void monster_start_go (edict_t *self); + + +void monster_triggered_spawn (edict_t *self) +{ + self->s.origin[2] += 1; + KillBox (self); + + self->solid = SOLID_BBOX; + self->movetype = MOVETYPE_STEP; + self->svflags &= ~SVF_NOCLIENT; + self->air_finished = level.time + 12; + gi.linkentity (self); + + monster_start_go (self); + + if (self->enemy && !(self->spawnflags & 1) && !(self->enemy->flags & FL_NOTARGET)) + { + FoundTarget (self); + } + else + { + self->enemy = NULL; + } +} + +void monster_triggered_spawn_use (edict_t *self, edict_t *other, edict_t *activator) +{ + // we have a one frame delay here so we don't telefrag the guy who activated us + self->think = monster_triggered_spawn; + self->nextthink = level.time + FRAMETIME; + if (activator->client) + self->enemy = activator; + self->use = monster_use; +} + +void monster_triggered_start (edict_t *self) +{ + self->solid = SOLID_NOT; + self->movetype = MOVETYPE_NONE; + self->svflags |= SVF_NOCLIENT; + self->nextthink = 0; + self->use = monster_triggered_spawn_use; +} +*/ + +/* +================ +monster_death_use + +When a monster dies, it fires all of its targets with the current +enemy as activator. +================ +*/ +/*void monster_death_use (edict_t *self) +{ + self->flags &= ~(FL_FLY|FL_SWIM); + self->monsterinfo.aiflags &= AI_GOOD_GUY; + + if (self->item) + { + Drop_Item (self, self->item); + self->item = NULL; + } + + if (self->deathtarget) + self->target = self->deathtarget; + + if (!self->target) + return; + + G_UseTargets (self, self->enemy); +} + + +//============================================================================ + +qboolean monster_start (edict_t *self) +{ + if (deathmatch->value ) + { + G_FreeEdict (self); + return false; + } + + if ((self->spawnflags & 4) && !(self->monsterinfo.aiflags & AI_GOOD_GUY)) + { + self->spawnflags &= ~4; + self->spawnflags |= 1; +// gi.dprintf("fixed spawnflags on %s at %s\n", self->classname, vtos(self->s.origin)); + } + + if (!(self->monsterinfo.aiflags & AI_GOOD_GUY)) + level.total_monsters++; + + self->nextthink = level.time + FRAMETIME; + self->svflags |= SVF_MONSTER; + self->s.renderfx |= RF_FRAMELERP; + self->takedamage = DAMAGE_AIM; + self->air_finished = level.time + 12; + self->use = monster_use; + self->max_health = self->health; + self->clipmask = MASK_MONSTERSOLID; + + self->s.skinnum = 0; + self->deadflag = DEAD_NO; + self->svflags &= ~SVF_DEADMONSTER; + + if (!self->monsterinfo.checkattack) + self->monsterinfo.checkattack = M_CheckAttack; + VectorCopy (self->s.origin, self->s.old_origin); + + if (st.item) + { + self->item = FindItemByClassname (st.item); + if (!self->item) + gi.dprintf("%s at %s has bad item: %s\n", self->classname, vtos(self->s.origin), st.item); + } + + // randomize what frame they start on + if (self->monsterinfo.currentmove) + self->s.frame = self->monsterinfo.currentmove->firstframe + (rand() % (self->monsterinfo.currentmove->lastframe - self->monsterinfo.currentmove->firstframe + 1)); + + return true; +} + +void monster_start_go (edict_t *self) +{ + vec3_t v; + + if (self->health <= 0) + return; + + // check for target to combat_point and change to combattarget + if (self->target) + { + qboolean notcombat; + qboolean fixup; + edict_t *target; + + target = NULL; + notcombat = false; + fixup = false; + while ((target = G_Find (target, FOFS(targetname), self->target)) != NULL) + { + if (strcmp(target->classname, "point_combat") == 0) + { + self->combattarget = self->target; + fixup = true; + } + else + { + notcombat = true; + } + } + if (notcombat && self->combattarget) + gi.dprintf("%s at %s has target with mixed types\n", self->classname, vtos(self->s.origin)); + if (fixup) + self->target = NULL; + } + + // validate combattarget + if (self->combattarget) + { + edict_t *target; + + target = NULL; + while ((target = G_Find (target, FOFS(targetname), self->combattarget)) != NULL) + { + if (strcmp(target->classname, "point_combat") != 0) + { + gi.dprintf("%s at (%i %i %i) has a bad combattarget %s : %s at (%i %i %i)\n", + self->classname, (int)self->s.origin[0], (int)self->s.origin[1], (int)self->s.origin[2], + self->combattarget, target->classname, (int)target->s.origin[0], (int)target->s.origin[1], + (int)target->s.origin[2]); + } + } + } + + if (self->target) + { + self->goalentity = self->movetarget = G_PickTarget(self->target); + if (!self->movetarget) + { + gi.dprintf ("%s can't find target %s at %s\n", self->classname, self->target, vtos(self->s.origin)); + self->target = NULL; + self->monsterinfo.pausetime = 100000000; + self->monsterinfo.stand (self); + } + else if (strcmp (self->movetarget->classname, "path_corner") == 0) + { + VectorSubtract (self->goalentity->s.origin, self->s.origin, v); + self->ideal_yaw = self->s.angles[YAW] = vectoyaw(v); + self->monsterinfo.walk (self); + self->target = NULL; + } + else + { + self->goalentity = self->movetarget = NULL; + self->monsterinfo.pausetime = 100000000; + self->monsterinfo.stand (self); + } + } + else + { + self->monsterinfo.pausetime = 100000000; + self->monsterinfo.stand (self); + } + + self->think = monster_think; + self->nextthink = level.time + FRAMETIME; +} + + +void walkmonster_start_go (edict_t *self) +{ + if (!(self->spawnflags & 2) && level.time < 1) + { + M_droptofloor (self); + + if (self->groundentity) + if (!M_walkmove (self, 0, 0)) + gi.dprintf ("%s in solid at %s\n", self->classname, vtos(self->s.origin)); + } + + if (!self->yaw_speed) + self->yaw_speed = 20; + self->viewheight = 25; + + monster_start_go (self); + + if (self->spawnflags & 2) + monster_triggered_start (self); +} + +void walkmonster_start (edict_t *self) +{ + self->think = walkmonster_start_go; + monster_start (self); +} + + +void flymonster_start_go (edict_t *self) +{ + if (!M_walkmove (self, 0, 0)) + gi.dprintf ("%s in solid at %s\n", self->classname, vtos(self->s.origin)); + + if (!self->yaw_speed) + self->yaw_speed = 10; + self->viewheight = 25; + + monster_start_go (self); + + if (self->spawnflags & 2) + monster_triggered_start (self); +} + + +void flymonster_start (edict_t *self) +{ + self->flags |= FL_FLY; + self->think = flymonster_start_go; + monster_start (self); +} + + +void swimmonster_start_go (edict_t *self) +{ + if (!self->yaw_speed) + self->yaw_speed = 10; + self->viewheight = 10; + + monster_start_go (self); + + if (self->spawnflags & 2) + monster_triggered_start (self); +} + +void swimmonster_start (edict_t *self) +{ + self->flags |= FL_SWIM; + self->think = swimmonster_start_go; + monster_start (self); +} +*/ \ No newline at end of file diff --git a/src/g_phys.c b/src/g_phys.c new file mode 100644 index 0000000..9f0850f --- /dev/null +++ b/src/g_phys.c @@ -0,0 +1,1042 @@ +// g_phys.c + +#include "g_local.h" + +/* + + +pushmove objects do not obey gravity, and do not interact with each other or trigger fields, but block normal movement and push normal objects when they move. + +onground is set for toss objects when they come to a complete rest. it is set for steping or walking objects + +doors, plats, etc are SOLID_BSP, and MOVETYPE_PUSH +bonus items are SOLID_TRIGGER touch, and MOVETYPE_TOSS +corpses are SOLID_NOT and MOVETYPE_TOSS +crates are SOLID_BBOX and MOVETYPE_TOSS +walking monsters are SOLID_SLIDEBOX and MOVETYPE_STEP +flying/floating monsters are SOLID_SLIDEBOX and MOVETYPE_FLY + +solid_edge items only clip against bsp models. + +*/ + + +/* +============ +SV_TestEntityPosition + +============ +*/ +edict_t *SV_TestEntityPosition (edict_t *ent) +{ + trace_t trace; + + trace = gi.trace (ent->s.origin, ent->mins, ent->maxs, ent->s.origin, ent, MASK_SOLID); + + if (trace.startsolid) + return g_edicts; + + return NULL; +} + + +/* +================ +SV_CheckVelocity +================ +*/ +void SV_CheckVelocity (edict_t *ent) +{ + int i; + +// +// bound velocity +// + for (i=0 ; i<3 ; i++) + { + if (ent->velocity[i] > sv_maxvelocity->value) + ent->velocity[i] = sv_maxvelocity->value; + else if (ent->velocity[i] < -sv_maxvelocity->value) + ent->velocity[i] = -sv_maxvelocity->value; + } +} + +/* +============= +SV_RunThink + +Runs thinking code for this frame if necessary +============= +*/ +qboolean SV_RunThink (edict_t *ent) +{ + float thinktime; + + thinktime = ent->nextthink; + if (thinktime <= 0) + return true; + if (thinktime > level.time+0.001) + return true; + + ent->nextthink = 0; + if (!ent->think) + gi.error ("NULL ent->think"); + ent->think (ent); + + return false; +} + +/* +================== +SV_Impact + +Two entities have touched, so run their touch functions +================== +*/ +void SV_Impact (edict_t *e1, trace_t *trace) +{ + edict_t *e2; +// cplane_t backplane; + + e2 = trace->ent; + + if (e1->touch && e1->solid != SOLID_NOT) + e1->touch (e1, e2, &trace->plane, trace->surface); + + if (e2->touch && e2->solid != SOLID_NOT) + e2->touch (e2, e1, NULL, NULL); +} + + +/* +================== +ClipVelocity + +Slide off of the impacting object +returns the blocked flags (1 = floor, 2 = step / wall) +================== +*/ +#define STOP_EPSILON 0.1 + +int ClipVelocity (vec3_t in, vec3_t normal, vec3_t out, float overbounce) +{ + float backoff; + float change; + int i, blocked; + + blocked = 0; + if (normal[2] > 0) + blocked |= 1; // floor + if (!normal[2]) + blocked |= 2; // step + + backoff = DotProduct (in, normal) * overbounce; + + for (i=0 ; i<3 ; i++) + { + change = normal[i]*backoff; + out[i] = in[i] - change; + if (out[i] > -STOP_EPSILON && out[i] < STOP_EPSILON) + out[i] = 0; + } + + return blocked; +} + + +/* +============ +SV_FlyMove + +The basic solid body movement clip that slides along multiple planes +Returns the clipflags if the velocity was modified (hit something solid) +1 = floor +2 = wall / step +4 = dead stop +============ +*/ +#define MAX_CLIP_PLANES 5 +int SV_FlyMove (edict_t *ent, float time, int mask) +{ + edict_t *hit; + int bumpcount, numbumps; + vec3_t dir; + float d; + int numplanes; + vec3_t planes[MAX_CLIP_PLANES]; + vec3_t primal_velocity, original_velocity, new_velocity; + int i, j; + trace_t trace; + vec3_t end; + float time_left; + int blocked; + + numbumps = 4; + + blocked = 0; + VectorCopy (ent->velocity, original_velocity); + VectorCopy (ent->velocity, primal_velocity); + numplanes = 0; + + time_left = time; + + ent->groundentity = NULL; + for (bumpcount=0 ; bumpcounts.origin[i] + time_left * ent->velocity[i]; + + trace = gi.trace (ent->s.origin, ent->mins, ent->maxs, end, ent, mask); + + if (trace.allsolid) + { // entity is trapped in another solid + VectorCopy (vec3_origin, ent->velocity); + return 3; + } + + if (trace.fraction > 0) + { // actually covered some distance + VectorCopy (trace.endpos, ent->s.origin); + VectorCopy (ent->velocity, original_velocity); + numplanes = 0; + } + + if (trace.fraction == 1) + break; // moved the entire distance + + hit = trace.ent; + + if (trace.plane.normal[2] > 0.7) + { + blocked |= 1; // floor + if ( hit->solid == SOLID_BSP) + { + ent->groundentity = hit; + ent->groundentity_linkcount = hit->linkcount; + } + } + if (!trace.plane.normal[2]) + { + blocked |= 2; // step + } + +// +// run the impact function +// + SV_Impact (ent, &trace); + if (!ent->inuse) + break; // removed by the impact function + + + time_left -= time_left * trace.fraction; + + // cliped to another plane + if (numplanes >= MAX_CLIP_PLANES) + { // this shouldn't really happen + VectorCopy (vec3_origin, ent->velocity); + return 3; + } + + VectorCopy (trace.plane.normal, planes[numplanes]); + numplanes++; + +// +// modify original_velocity so it parallels all of the clip planes +// +//numplanes = 0; +//PON-CTF + i = false; + if(ctf->value) + { + if(ent->client->ctf_grapple != NULL + && ent->client->ctf_grapplestate != CTF_GRAPPLE_STATE_FLY + /*&& ent->waterlevel <= 1*/) i = true; + } + if(!i){if(!ent->groundentity) i = true;} +//PON-CTF + if(!i/*ent->client*/) + { + if(!ent->client->zc.trapped + && !i) + { + numplanes = 0; + if(ent->waterlevel || (!ent->groundentity && ent->velocity[2] > 10 )) goto VELCX; + i =0; + if(/*ent->groundentity ||*/ ent->velocity[2] > 10) goto VELC; + } + } + + for (i=0 ; ivelocity); + } + else + { // go along the crease + if (numplanes != 2) + { +// gi.dprintf ("clip velocity, numplanes == %i\n",numplanes); + VectorCopy (vec3_origin, ent->velocity); + return 7; + } + CrossProduct (planes[0], planes[1], dir); + d = DotProduct (dir, ent->velocity); + VectorScale (dir, d, ent->velocity); + } +VELCX: +// +// if original velocity is against the original velocity, stop dead +// to avoid tiny occilations in sloping corners +// + if (DotProduct (ent->velocity, primal_velocity) <= 0) + { + VectorCopy (vec3_origin, ent->velocity); + return blocked; + } + } + + return blocked; +} + + +/* +============ +SV_AddGravity + +============ +*/ +void SV_AddGravity (edict_t *ent) +{ +// gi.bprintf(PRINT_HIGH,"gravadd %f\n",ent->gravity * sv_gravity->value * FRAMETIME); + ent->velocity[2] -= ent->gravity * sv_gravity->value * FRAMETIME; +} + +/* +=============================================================================== + +PUSHMOVE + +=============================================================================== +*/ + +/* +============ +SV_PushEntity + +Does not change the entities velocity at all +============ +*/ +trace_t SV_PushEntity (edict_t *ent, vec3_t push) +{ + trace_t trace; + vec3_t start; + vec3_t end; + int mask; + + VectorCopy (ent->s.origin, start); + VectorAdd (start, push, end); + +retry: + if (ent->clipmask) + mask = ent->clipmask; + else + mask = MASK_SOLID; + + trace = gi.trace (start, ent->mins, ent->maxs, end, ent, mask); + + VectorCopy (trace.endpos, ent->s.origin); + gi.linkentity (ent); + + if (trace.fraction != 1.0) + { + SV_Impact (ent, &trace); + + // if the pushed entity went away and the pusher is still there + if (!trace.ent->inuse && ent->inuse) + { + // move the pusher back and try again + VectorCopy (start, ent->s.origin); + gi.linkentity (ent); + goto retry; + } + } + + if (ent->inuse) + G_TouchTriggers (ent); + + return trace; +} + + +typedef struct +{ + edict_t *ent; + vec3_t origin; + vec3_t angles; + float deltayaw; +} pushed_t; +pushed_t pushed[MAX_EDICTS], *pushed_p; + +edict_t *obstacle; + +/* +============ +SV_Push + +Objects need to be moved back on a failed push, +otherwise riders would continue to slide. +============ +*/ +qboolean SV_Push (edict_t *pusher, vec3_t move, vec3_t amove) +{ + int i, e; + edict_t *check, *block; + vec3_t mins, maxs; + pushed_t *p; + vec3_t org, org2, move2, forward, right, up; + + // clamp the move to 1/8 units, so the position will + // be accurate for client side prediction + for (i=0 ; i<3 ; i++) + { + float temp; + temp = move[i]*8.0; + if (temp > 0.0) + temp += 0.5; + else + temp -= 0.5; + move[i] = 0.125 * (int)temp; + } + + // find the bounding box + for (i=0 ; i<3 ; i++) + { + mins[i] = pusher->absmin[i] + move[i]; + maxs[i] = pusher->absmax[i] + move[i]; + } + +// we need this for pushing things later + VectorSubtract (vec3_origin, amove, org); + AngleVectors (org, forward, right, up); + +// save the pusher's original position + pushed_p->ent = pusher; + VectorCopy (pusher->s.origin, pushed_p->origin); + VectorCopy (pusher->s.angles, pushed_p->angles); + if (pusher->client) + pushed_p->deltayaw = pusher->client->ps.pmove.delta_angles[YAW]; + pushed_p++; + +// move the pusher to it's final position + VectorAdd (pusher->s.origin, move, pusher->s.origin); + VectorAdd (pusher->s.angles, amove, pusher->s.angles); + gi.linkentity (pusher); + +// see if any solid entities are inside the final position + check = g_edicts+1; + for (e = 1; e < globals.num_edicts; e++, check++) + { + if (!check->inuse) + continue; + if (!check->solid) continue; + +//ponko + if(check->classname[0] == 'R' && (check->classname[6] == 'X' || check->classname[6] == '3') ) continue; + + if (check->movetype == MOVETYPE_PUSH + || check->movetype == MOVETYPE_STOP + || check->movetype == MOVETYPE_NONE + || check->movetype == MOVETYPE_NOCLIP) + continue; + +// if(check->movetype == MOVETYPE_STEP) M_CheckGround(check); + if (!check->area.prev) + continue; // not linked in anywhere + + // if the entity is standing on the pusher, it will definitely be moved + if (check->groundentity != pusher) + { + // see if the ent needs to be tested + if ( check->absmin[0] >= maxs[0] + || check->absmin[1] >= maxs[1] + || check->absmin[2] >= maxs[2] + || check->absmax[0] <= mins[0] + || check->absmax[1] <= mins[1] + || check->absmax[2] <= mins[2] ) + continue; + + // see if the ent's bbox is inside the pusher's final position + if (!SV_TestEntityPosition (check)) + continue; + } + + if ((pusher->movetype == MOVETYPE_PUSH) || (check->groundentity == pusher)) + { + // move this entity + pushed_p->ent = check; + VectorCopy (check->s.origin, pushed_p->origin); + VectorCopy (check->s.angles, pushed_p->angles); + pushed_p++; + + // try moving the contacted entity + VectorAdd (check->s.origin, move, check->s.origin); + if (check->client) + { // FIXME: doesn't rotate monsters? + check->client->ps.pmove.delta_angles[YAW] += amove[YAW]; + } + + // figure movement due to the pusher's amove + VectorSubtract (check->s.origin, pusher->s.origin, org); + org2[0] = DotProduct (org, forward); + org2[1] = -DotProduct (org, right); + org2[2] = DotProduct (org, up); + VectorSubtract (org2, org, move2); + VectorAdd (check->s.origin, move2, check->s.origin); + + // may have pushed them off an edge + if (check->groundentity != pusher) + check->groundentity = NULL; + + block = SV_TestEntityPosition (check); + if (!block) + { // pushed ok + gi.linkentity (check); + // impact? + continue; + } + + // if it is ok to leave in the old position, do it + // this is only relevent for riding entities, not pushed + // FIXME: this doesn't acount for rotation + VectorSubtract (check->s.origin, move, check->s.origin); + block = SV_TestEntityPosition (check); + if (!block) + { + pushed_p--; + continue; + } + } + + // save off the obstacle so we can call the block function + obstacle = check; + + // move back any entities we already moved + // go backwards, so if the same entity was pushed + // twice, it goes back to the original position + for (p=pushed_p-1 ; p>=pushed ; p--) + { + VectorCopy (p->origin, p->ent->s.origin); + VectorCopy (p->angles, p->ent->s.angles); + if (p->ent->client) + { + p->ent->client->ps.pmove.delta_angles[YAW] = p->deltayaw; + } + gi.linkentity (p->ent); + } + return false; + } + +//FIXME: is there a better way to handle this? + // see if anything we moved has touched a trigger + for (p=pushed_p-1 ; p>=pushed ; p--) + G_TouchTriggers (p->ent); + + return true; +} + +/* +================ +SV_Physics_Pusher + +Bmodel objects don't interact with each other, but +push all box objects +================ +*/ +void SV_Physics_Pusher (edict_t *ent) +{ + vec3_t move, amove; + edict_t *part, *mv; + + // if not a team captain, so movement will be handled elsewhere + if ( ent->flags & FL_TEAMSLAVE) + return; + + // make sure all team slaves can move before commiting + // any moves or calling any think functions + // if the move is blocked, all moved objects will be backed out +//retry: + pushed_p = pushed; + for (part = ent ; part ; part=part->teamchain) + { + if (part->velocity[0] || part->velocity[1] || part->velocity[2] || + part->avelocity[0] || part->avelocity[1] || part->avelocity[2] + ) + { // object is moving + VectorScale (part->velocity, FRAMETIME, move); + VectorScale (part->avelocity, FRAMETIME, amove); + + if (!SV_Push (part, move, amove)) + break; // move was blocked + } + } + if (pushed_p > &pushed[MAX_EDICTS]) + gi.error (ERR_FATAL, "pushed_p > &pushed[MAX_EDICTS], memory corrupted"); + + if (part) + { + // the move failed, bump all nextthink times and back out moves + for (mv = ent ; mv ; mv=mv->teamchain) + { + if (mv->nextthink > 0) + mv->nextthink += FRAMETIME; + } + + // if the pusher has a "blocked" function, call it + // otherwise, just stay in place until the obstacle is gone + if (part->blocked) + { + part->blocked (part, obstacle); + } + +#if 0 + // if the pushed entity went away and the pusher is still there + if (!obstacle->inuse && part->inuse) + goto retry; +#endif + } + else + { + // the move succeeded, so call all think functions + for (part = ent ; part ; part=part->teamchain) + { + SV_RunThink (part); + } + } +} + +//================================================================== + +/* +============= +SV_Physics_None + +Non moving objects can only think +============= +*/ +void SV_Physics_None (edict_t *ent) +{ +// regular thinking + SV_RunThink (ent); +} + +/* +============= +SV_Physics_Noclip + +A moving object that doesn't obey physics +============= +*/ +void SV_Physics_Noclip (edict_t *ent) +{ +// regular thinking + if (!SV_RunThink (ent)) + return; + + VectorMA (ent->s.angles, FRAMETIME, ent->avelocity, ent->s.angles); + VectorMA (ent->s.origin, FRAMETIME, ent->velocity, ent->s.origin); + + gi.linkentity (ent); +} + +/* +============================================================================== + +TOSS / BOUNCE + +============================================================================== +*/ + +/* +============= +SV_Physics_Toss + +Toss, bounce, and fly movement. When onground, do nothing. +============= +*/ +void SV_Physics_Toss (edict_t *ent) +{ + trace_t trace; + vec3_t move; + float backoff; + edict_t *slave; + qboolean wasinwater; + qboolean isinwater; + vec3_t old_origin; + + qboolean forcethrough = false; + +// regular thinking + SV_RunThink (ent); + + // if not a team captain, so movement will be handled elsewhere + if ( ent->flags & FL_TEAMSLAVE) + return; + + if (ent->velocity[2] > 0) + ent->groundentity = NULL; + +// check for the groundentity going away + if (ent->groundentity) + if (!ent->groundentity->inuse) + ent->groundentity = NULL; + +// if onground, return without moving + if ( ent->groundentity ) + return; + + VectorCopy (ent->s.origin, old_origin); + + SV_CheckVelocity (ent); + +// add gravity + if (ent->movetype != MOVETYPE_FLY + && ent->movetype != MOVETYPE_FLYMISSILE + // RAFAEL + // move type for rippergun projectile + && ent->movetype != MOVETYPE_WALLBOUNCE) + SV_AddGravity (ent); + +// move angles + VectorMA (ent->s.angles, FRAMETIME, ent->avelocity, ent->s.angles); + +// move origin + VectorScale (ent->velocity, FRAMETIME, move); + + + if(ent->classname[0] == 'R' && (ent->classname[6] == 'X' || ent->classname[6] == '3')) + { + ent->groundentity = ent->union_ent; + ent->groundentity_linkcount = ent->union_ent->linkcount; + VectorCopy (ent->union_ent->velocity, ent->velocity); + VectorCopy (ent->union_ent->avelocity, ent->avelocity); + + VectorAdd(ent->union_ent->s.origin,ent->moveinfo.dir,ent->s.origin); + } + trace = SV_PushEntity (ent, move); + + if (!ent->inuse) + return; + + + if (trace.fraction < 1 && !forcethrough) + { + // RAFAEL + if (ent->movetype == MOVETYPE_WALLBOUNCE) + backoff = 2.0; + // RAFAEL ( else ) + else if (ent->movetype == MOVETYPE_BOUNCE) + backoff = 1.5; + else + backoff = 1; + + ClipVelocity (ent->velocity, trace.plane.normal, ent->velocity, backoff); + + // RAFAEL + if (ent->movetype == MOVETYPE_WALLBOUNCE) + vectoangles (ent->velocity, ent->s.angles); + + // stop if on ground + // RAFAEL + if (trace.plane.normal[2] > 0.7 && ent->movetype != MOVETYPE_WALLBOUNCE) + { + if (ent->velocity[2] < 60 || ent->movetype != MOVETYPE_BOUNCE ) + { + ent->groundentity = trace.ent; + ent->groundentity_linkcount = trace.ent->linkcount; + VectorCopy (vec3_origin, ent->velocity); + VectorCopy (vec3_origin, ent->avelocity); + } + } + +// if (ent->touch) +// ent->touch (ent, trace.ent, &trace.plane, trace.surface); + } + +// check for water transition + wasinwater = (ent->watertype & MASK_WATER); + ent->watertype = gi.pointcontents (ent->s.origin); + isinwater = ent->watertype & MASK_WATER; + + if (isinwater) + ent->waterlevel = 1; + else + ent->waterlevel = 0; + + if (!wasinwater && isinwater) + gi.positioned_sound (old_origin, g_edicts, CHAN_AUTO, gi.soundindex("misc/h2ohit1.wav"), 1, 1, 0); + else if (wasinwater && !isinwater) + gi.positioned_sound (ent->s.origin, g_edicts, CHAN_AUTO, gi.soundindex("misc/h2ohit1.wav"), 1, 1, 0); + +// move teamslaves + for (slave = ent->teamchain; slave; slave = slave->teamchain) + { + VectorCopy (ent->s.origin, slave->s.origin); + gi.linkentity (slave); + } +} + +/* +=============================================================================== + +STEPPING MOVEMENT + +=============================================================================== +*/ + +/* +============= +SV_Physics_Step + +Monsters freefall when they don't have a ground entity, otherwise +all movement is done with discrete steps. + +This is also used for objects that have become still on the ground, but +will fall if the floor is pulled out from under them. +FIXME: is this true? +============= +*/ + +//FIXME: hacked in for E3 demo +#define sv_stopspeed 100 +#define sv_friction 6 +#define sv_waterfriction 1 + +void SV_AddRotationalFriction (edict_t *ent) +{ + int n; + float adjustment; + + VectorMA (ent->s.angles, FRAMETIME, ent->avelocity, ent->s.angles); + adjustment = FRAMETIME * sv_stopspeed * sv_friction; + for (n = 0; n < 3; n++) + { + if (ent->avelocity[n] > 0) + { + ent->avelocity[n] -= adjustment; + if (ent->avelocity[n] < 0) + ent->avelocity[n] = 0; + } + else + { + ent->avelocity[n] += adjustment; + if (ent->avelocity[n] > 0) + ent->avelocity[n] = 0; + } + } +} + +void P_FallingDamage (edict_t *ent); +void SV_Physics_Step (edict_t *ent) +{ + qboolean wasonground; + qboolean hitsound = false; + float *vel; + float speed, newspeed, control; + float friction; + edict_t *groundentity; + int mask; +// vec3_t dir; + + + // airborn monsters should always check for ground +// if (!ent->groundentity) + M_CheckGround (ent); + + groundentity = ent->groundentity; + + SV_CheckVelocity (ent); + + if (groundentity) + wasonground = true; + else + wasonground = false; + +// if (ent->avelocity[0] || ent->avelocity[1] || ent->avelocity[2]) +// SV_AddRotationalFriction (ent); + + // add gravity except: + // flying monsters + // swimming monsters who are in the water + if (! wasonground) + if (!(ent->flags & FL_FLY)) + if (!((ent->flags & FL_SWIM) && (ent->waterlevel > 2))) + { + if (ent->velocity[2] < sv_gravity->value*-0.1) + hitsound = true; + if (ent->waterlevel == 0) + SV_AddGravity (ent); + } + + // friction for flying monsters that have been given vertical velocity + if ((ent->flags & FL_FLY) && (ent->velocity[2] != 0)) + { +//gi.bprintf(PRINT_HIGH,"FLY!\n"); + speed = fabs(ent->velocity[2]); + control = speed < sv_stopspeed ? sv_stopspeed : speed; + friction = sv_friction/3; + newspeed = speed - (FRAMETIME * control * friction); + if (newspeed < 0) + newspeed = 0; + newspeed /= speed; + ent->velocity[2] *= newspeed; + } + + // friction for flying monsters that have been given vertical velocity + if ((ent->flags & FL_SWIM) && (ent->velocity[2] != 0)) + { +//gi.bprintf(PRINT_HIGH,"SWIM!\n"); + speed = fabs(ent->velocity[2]); + control = speed < sv_stopspeed ? sv_stopspeed : speed; + newspeed = speed - (FRAMETIME * control * sv_waterfriction * ent->waterlevel); + if (newspeed < 0) + newspeed = 0; + newspeed /= speed; + ent->velocity[2] *= newspeed; + } + +// if(ent->client){if(ent->client->ctf_grapple) if(ent->client->ctf_grapplestate == CTF_GRAPPLE_STATE_FLY) ent->velocity[2] = 0;} + + if (ent->velocity[2] || ent->velocity[1] || ent->velocity[0]) + { + // apply friction + // let dead monsters who aren't completely onground slide + if ((wasonground) || (ent->flags & (FL_SWIM|FL_FLY))) + if (!(ent->health <= 0.0 && !M_CheckBottom(ent))) + { + vel = ent->velocity; + speed = sqrt(vel[0]*vel[0] +vel[1]*vel[1]); + if (speed) + { + friction = sv_friction; + + control = speed < sv_stopspeed ? sv_stopspeed : speed; + newspeed = speed - FRAMETIME*control*friction; + + if (newspeed < 0) + newspeed = 0; + newspeed /= speed; + + vel[0] *= newspeed; + vel[1] *= newspeed; + } + } + + if (ent->svflags & SVF_MONSTER) + { + if(!deathmatch->value) mask = MASK_MONSTERSOLID; + else mask = MASK_BOTSOLIDX;//MASK_PLAYERSOLID; + } + else + mask = MASK_SOLID; + SV_FlyMove (ent, FRAMETIME, mask); + + gi.linkentity (ent); +// G_TouchTriggers (ent); + +/* if (ent->groundentity) + { + if (!wasonground) + { + speed = ent->s.old_origin[2] - ent->s.origin[2]; + VectorSubtract(ent->s.old_origin,ent->s.origin,dir); + VectorNormalize(dir); + if (hitsound && !ent->waterlevel && speed > 0) + { + + if( speed < 5 ) gi.sound (ent, 0, gi.soundindex("player/land1.wav"), 1, 1, 0); + else if( speed < 40 || ent->client == NULL) + { + gi.sound (ent, 0, gi.soundindex("player/step3.wav"), 1, 1, 0); +// gi.bprintf(PRINT_HIGH,"level 1\n"); + } + else if( speed < 60 ) + { + gi.sound (ent, 0, gi.soundindex("*fall2.wav"), 1, 1, 0); + ent->pain_debounce_time = level.time + FRAMETIME * 10; + T_Damage (ent, world, world, dir, ent->s.origin, ent->s.origin, (int)(random()*(15)), 0, 0, MOD_FALLING); +// gi.bprintf(PRINT_HIGH,"level 2\n"); + } + else + { + gi.sound (ent, 0, gi.soundindex("*fall1.wav"), 1, 1, 0); + ent->pain_debounce_time = level.time + FRAMETIME * 10; + T_Damage (ent, world, world, dir, ent->s.origin, ent->s.origin, (int)(random() * (25)), 0, 0, MOD_FALLING); +// gi.bprintf(PRINT_HIGH,"level 3\n"); + } + } + } + }*/ + } + +// regular thinking + SV_RunThink (ent); +} + +//============================================================================ +/* +================ +G_RunEntity + +================ +*/ +void G_RunEntity (edict_t *ent) +{ + + if (ent->prethink) + ent->prethink (ent); + + switch ( (int)ent->movetype) + { + case MOVETYPE_PUSH: + case MOVETYPE_STOP: + SV_Physics_Pusher (ent); + break; + case MOVETYPE_NONE: + SV_Physics_None (ent); + break; + case MOVETYPE_NOCLIP: + SV_Physics_Noclip (ent); + break; + case MOVETYPE_STEP: + SV_Physics_Step (ent); + break; + case MOVETYPE_TOSS: + case MOVETYPE_BOUNCE: + case MOVETYPE_FLY: + case MOVETYPE_FLYMISSILE: + SV_Physics_Toss (ent); + break; + // RAFAEL + case MOVETYPE_WALLBOUNCE: + SV_Physics_Toss (ent); + break; + default: + gi.error ("SV_Physics: bad movetype %i", (int)ent->movetype); + } +} diff --git a/src/g_save.c b/src/g_save.c new file mode 100644 index 0000000..6936eca --- /dev/null +++ b/src/g_save.c @@ -0,0 +1,736 @@ + +#include "g_local.h" +#include "bot.h" + +field_t fields[] = { + {"classname", FOFS(classname), F_LSTRING}, + {"origin", FOFS(s.origin), F_VECTOR}, + {"model", FOFS(model), F_LSTRING}, + {"spawnflags", FOFS(spawnflags), F_INT}, + {"speed", FOFS(speed), F_FLOAT}, + {"accel", FOFS(accel), F_FLOAT}, + {"decel", FOFS(decel), F_FLOAT}, + {"target", FOFS(target), F_LSTRING}, + {"targetname", FOFS(targetname), F_LSTRING}, + {"pathtarget", FOFS(pathtarget), F_LSTRING}, + {"deathtarget", FOFS(deathtarget), F_LSTRING}, + {"killtarget", FOFS(killtarget), F_LSTRING}, + {"combattarget", FOFS(combattarget), F_LSTRING}, + {"message", FOFS(message), F_LSTRING}, + {"team", FOFS(team), F_LSTRING}, + {"wait", FOFS(wait), F_FLOAT}, + {"delay", FOFS(delay), F_FLOAT}, + {"random", FOFS(random), F_FLOAT}, + {"move_origin", FOFS(move_origin), F_VECTOR}, + {"move_angles", FOFS(move_angles), F_VECTOR}, + {"style", FOFS(style), F_INT}, + {"count", FOFS(count), F_INT}, + {"health", FOFS(health), F_INT}, + {"sounds", FOFS(sounds), F_INT}, + {"light", 0, F_IGNORE}, + {"dmg", FOFS(dmg), F_INT}, + {"angles", FOFS(s.angles), F_VECTOR}, + {"angle", FOFS(s.angles), F_ANGLEHACK}, + {"mass", FOFS(mass), F_INT}, + {"volume", FOFS(volume), F_FLOAT}, + {"attenuation", FOFS(attenuation), F_FLOAT}, + {"map", FOFS(map), F_LSTRING}, + //arena + {"arena", FOFS(arena),F_INT}, +//end arena + // temp spawn vars -- only valid when the spawn function is called + {"lip", STOFS(lip), F_INT, FFL_SPAWNTEMP}, + {"distance", STOFS(distance), F_INT, FFL_SPAWNTEMP}, + {"height", STOFS(height), F_INT, FFL_SPAWNTEMP}, + {"noise", STOFS(noise), F_LSTRING, FFL_SPAWNTEMP}, + {"pausetime", STOFS(pausetime), F_FLOAT, FFL_SPAWNTEMP}, + {"item", STOFS(item), F_LSTRING, FFL_SPAWNTEMP}, + {"gravity", STOFS(gravity), F_LSTRING, FFL_SPAWNTEMP}, + {"sky", STOFS(sky), F_LSTRING, FFL_SPAWNTEMP}, + {"skyrotate", STOFS(skyrotate), F_FLOAT, FFL_SPAWNTEMP}, + {"skyaxis", STOFS(skyaxis), F_VECTOR, FFL_SPAWNTEMP}, + {"minyaw", STOFS(minyaw), F_FLOAT, FFL_SPAWNTEMP}, + {"maxyaw", STOFS(maxyaw), F_FLOAT, FFL_SPAWNTEMP}, + {"minpitch", STOFS(minpitch), F_FLOAT, FFL_SPAWNTEMP}, + {"maxpitch", STOFS(maxpitch), F_FLOAT, FFL_SPAWNTEMP}, + {"nextmap", STOFS(nextmap), F_LSTRING, FFL_SPAWNTEMP} +}; + +// -------- just for savegames ---------- +// all pointer fields should be listed here, or savegames +// won't work properly (they will crash and burn). +// this wasn't just tacked on to the fields array, because +// these don't need names, we wouldn't want map fields using +// some of these, and if one were accidentally present twice +// it would double swizzle (fuck) the pointer. + +field_t savefields[] = +{ + {"", FOFS(classname), F_LSTRING}, + {"", FOFS(target), F_LSTRING}, + {"", FOFS(targetname), F_LSTRING}, + {"", FOFS(killtarget), F_LSTRING}, + {"", FOFS(team), F_LSTRING}, + {"", FOFS(pathtarget), F_LSTRING}, + {"", FOFS(deathtarget), F_LSTRING}, + {"", FOFS(combattarget), F_LSTRING}, + {"", FOFS(model), F_LSTRING}, + {"", FOFS(map), F_LSTRING}, + {"", FOFS(message), F_LSTRING}, + + {"", FOFS(client), F_CLIENT}, + {"", FOFS(item), F_ITEM}, + + {"", FOFS(goalentity), F_EDICT}, + {"", FOFS(movetarget), F_EDICT}, + {"", FOFS(enemy), F_EDICT}, + {"", FOFS(oldenemy), F_EDICT}, + {"", FOFS(activator), F_EDICT}, + {"", FOFS(groundentity), F_EDICT}, + {"", FOFS(teamchain), F_EDICT}, + {"", FOFS(teammaster), F_EDICT}, + {"", FOFS(owner), F_EDICT}, + {"", FOFS(mynoise), F_EDICT}, + {"", FOFS(mynoise2), F_EDICT}, + {"", FOFS(target_ent), F_EDICT}, + {"", FOFS(chain), F_EDICT}, + + {NULL, 0, F_INT} +}; + +field_t levelfields[] = +{ + {"", LLOFS(changemap), F_LSTRING}, + + {"", LLOFS(sight_client), F_EDICT}, + {"", LLOFS(sight_entity), F_EDICT}, + {"", LLOFS(sound_entity), F_EDICT}, + {"", LLOFS(sound2_entity), F_EDICT}, + + {NULL, 0, F_INT} +}; + +field_t clientfields[] = +{ + {"", CLOFS(pers.weapon), F_ITEM}, + {"", CLOFS(pers.lastweapon), F_ITEM}, + {"", CLOFS(newweapon), F_ITEM}, + + {NULL, 0, F_INT} +}; + +/* +============ +InitGame + +This will be called when the dll is first loaded, which +only happens when a new game is started or a save game +is loaded. +============ +*/ +void SetBotFlag1(edict_t *ent); //チーム1の旗 +void SetBotFlag2(edict_t *ent); //チーム2の旗 +void InitGame (void) +{ + gi.dprintf ("==== InitGame ====\n"); + + bot_team_flag1 = NULL; + bot_team_flag2 = NULL; +// SetBotFlag1(NULL); +// SetBotFlag2(NULL); + + gun_x = gi.cvar ("gun_x", "0", 0); + gun_y = gi.cvar ("gun_y", "0", 0); + gun_z = gi.cvar ("gun_z", "0", 0); + + //FIXME: sv_ prefix is wrong for these + sv_rollspeed = gi.cvar ("sv_rollspeed", "200", 0); + sv_rollangle = gi.cvar ("sv_rollangle", "2", 0); + sv_maxvelocity = gi.cvar ("sv_maxvelocity", "2000", 0); + sv_gravity = gi.cvar ("sv_gravity", "800", 0); + + // noset vars + dedicated = gi.cvar ("dedicated", "0", CVAR_NOSET); + + // latched vars + sv_cheats = gi.cvar ("cheats", "0", CVAR_SERVERINFO|CVAR_LATCH); + gi.cvar ("gamename", GAMEVERSION , CVAR_SERVERINFO | CVAR_LATCH); + gi.cvar ("gamedate", __DATE__ , CVAR_SERVERINFO | CVAR_LATCH); + + maxclients = gi.cvar ("maxclients", "4", CVAR_SERVERINFO | CVAR_LATCH); + deathmatch = gi.cvar ("deathmatch", "0", CVAR_LATCH); + coop = gi.cvar ("coop", "0", CVAR_LATCH); + skill = gi.cvar ("skill", "1", CVAR_LATCH); + maxentities = gi.cvar ("maxentities", "1024", CVAR_LATCH); + //maplist value + maplist = gi.cvar ("maplist", "default", CVAR_SERVERINFO | CVAR_LATCH); + //autospawn + autospawn = gi.cvar ("autospawn", "0", CVAR_SERVERINFO | CVAR_LATCH); + //chain edit flag + chedit = gi.cvar ("chedit", "0", CVAR_LATCH); + //vwep support + vwep = gi.cvar ("vwep", "1", CVAR_LATCH); + //game mode + zigmode = gi.cvar ("zigmode", "0", CVAR_SERVERINFO| CVAR_LATCH); +//ZOID +//This game.dll only supports deathmatch + if (!deathmatch->value) { + gi.dprintf("Forcing deathmatch.\n"); + gi.cvar_set("deathmatch", "1"); + } + //force coop off + if (coop->value) + gi.cvar_set("coop", "0"); +//ZOID + + // change anytime vars + dmflags = gi.cvar ("dmflags", "0", CVAR_SERVERINFO); + fraglimit = gi.cvar ("fraglimit", "0", CVAR_SERVERINFO); + timelimit = gi.cvar ("timelimit", "0", CVAR_SERVERINFO); +//ZOID + capturelimit = gi.cvar ("capturelimit", "0", CVAR_SERVERINFO); +//ZOID + password = gi.cvar ("password", "", CVAR_USERINFO); + spectator_password = gi.cvar ("spectator_password", "", CVAR_USERINFO); + maxspectators = gi.cvar ("maxspectators", "4", CVAR_SERVERINFO); + g_select_empty = gi.cvar ("g_select_empty", "0", CVAR_ARCHIVE); + + filterban = gi.cvar ("filterban", "1", 0); + + run_pitch = gi.cvar ("run_pitch", "0.002", 0); + run_roll = gi.cvar ("run_roll", "0.005", 0); + bob_up = gi.cvar ("bob_up", "0.005", 0); + bob_pitch = gi.cvar ("bob_pitch", "0.002", 0); + bob_roll = gi.cvar ("bob_roll", "0.002", 0); + + // items + InitItems (); + + Com_sprintf (game.helpmessage1, sizeof(game.helpmessage1), ""); + + Com_sprintf (game.helpmessage2, sizeof(game.helpmessage2), ""); + + // initialize all entities for this game + game.maxentities = maxentities->value; + g_edicts = gi.TagMalloc (game.maxentities * sizeof(g_edicts[0]), TAG_GAME); + globals.edicts = g_edicts; + globals.max_edicts = game.maxentities; + + // initialize all clients for this game + game.maxclients = maxclients->value; + game.clients = gi.TagMalloc (game.maxclients * sizeof(game.clients[0]), TAG_GAME); + globals.num_edicts = game.maxclients+1; + +//ZOID + CTFInit(); +//ZOID + Load_BotInfo(); //コンフィグ読み込み3ZBConfig.cfg +} + +//========================================================= + +void WriteField1 (FILE *f, field_t *field, byte *base) +{ + void *p; + int len; + int index; + + p = (void *)(base + field->ofs); + switch (field->type) + { + case F_INT: + case F_FLOAT: + case F_ANGLEHACK: + case F_VECTOR: + case F_IGNORE: + break; + + case F_LSTRING: + case F_GSTRING: + if ( *(char **)p ) + len = strlen(*(char **)p) + 1; + else + len = 0; + *(int *)p = len; + break; + case F_EDICT: + if ( *(edict_t **)p == NULL) + index = -1; + else + index = *(edict_t **)p - g_edicts; + *(int *)p = index; + break; + case F_CLIENT: + if ( *(gclient_t **)p == NULL) + index = -1; + else + index = *(gclient_t **)p - game.clients; + *(int *)p = index; + break; + case F_ITEM: + if ( *(edict_t **)p == NULL) + index = -1; + else + index = *(gitem_t **)p - itemlist; + *(int *)p = index; + break; + + default: + gi.error ("WriteEdict: unknown field type"); + } +} + +void WriteField2 (FILE *f, field_t *field, byte *base) +{ + int len; + void *p; + + p = (void *)(base + field->ofs); + switch (field->type) + { + case F_LSTRING: + case F_GSTRING: + if ( *(char **)p ) + { + len = strlen(*(char **)p) + 1; + fwrite (*(char **)p, len, 1, f); + } + break; + } +} + +void ReadField (FILE *f, field_t *field, byte *base) +{ + void *p; + int len; + int index; + + p = (void *)(base + field->ofs); + switch (field->type) + { + case F_INT: + case F_FLOAT: + case F_ANGLEHACK: + case F_VECTOR: + case F_IGNORE: + break; + + case F_LSTRING: + len = *(int *)p; + if (!len) + *(char **)p = NULL; + else + { + *(char **)p = gi.TagMalloc (len, TAG_LEVEL); + fread (*(char **)p, len, 1, f); + } + break; + case F_GSTRING: + len = *(int *)p; + if (!len) + *(char **)p = NULL; + else + { + *(char **)p = gi.TagMalloc (len, TAG_GAME); + fread (*(char **)p, len, 1, f); + } + break; + case F_EDICT: + index = *(int *)p; + if ( index == -1 ) + *(edict_t **)p = NULL; + else + *(edict_t **)p = &g_edicts[index]; + break; + case F_CLIENT: + index = *(int *)p; + if ( index == -1 ) + *(gclient_t **)p = NULL; + else + *(gclient_t **)p = &game.clients[index]; + break; + case F_ITEM: + index = *(int *)p; + if ( index == -1 ) + *(gitem_t **)p = NULL; + else + *(gitem_t **)p = &itemlist[index]; + break; + + default: + gi.error ("ReadEdict: unknown field type"); + } +} + +//========================================================= + +/* +============== +WriteClient + +All pointer variables (except function pointers) must be handled specially. +============== +*/ +void WriteClient (FILE *f, gclient_t *client) +{ + field_t *field; + gclient_t temp; + + // all of the ints, floats, and vectors stay as they are + temp = *client; + + // change the pointers to lengths or indexes + for (field=clientfields ; field->name ; field++) + { + WriteField1 (f, field, (byte *)&temp); + } + + // write the block + fwrite (&temp, sizeof(temp), 1, f); + + // now write any allocated data following the edict + for (field=clientfields ; field->name ; field++) + { + WriteField2 (f, field, (byte *)client); + } +} + +/* +============== +ReadClient + +All pointer variables (except function pointers) must be handled specially. +============== +*/ +void ReadClient (FILE *f, gclient_t *client) +{ + field_t *field; + + fread (client, sizeof(*client), 1, f); + + for (field=clientfields ; field->name ; field++) + { + ReadField (f, field, (byte *)client); + } +} + +/* +============ +WriteGame + +This will be called whenever the game goes to a new level, +and when the user explicitly saves the game. + +Game information include cross level data, like multi level +triggers, help computer info, and all client states. + +A single player death will automatically restore from the +last save position. +============ +*/ +void WriteGame (char *filename, qboolean autosave) +{ + FILE *f; + int i; + char str[16]; + + if (!autosave) + SaveClientData (); + + f = fopen (filename, "wb"); + if (!f) + gi.error ("Couldn't open %s", filename); + + memset (str, 0, sizeof(str)); + strcpy (str, __DATE__); + fwrite (str, sizeof(str), 1, f); + + game.autosaved = autosave; + fwrite (&game, sizeof(game), 1, f); + game.autosaved = false; + + for (i=0 ; iname ; field++) + { + WriteField1 (f, field, (byte *)&temp); + } + + // write the block + fwrite (&temp, sizeof(temp), 1, f); + + // now write any allocated data following the edict + for (field=savefields ; field->name ; field++) + { + WriteField2 (f, field, (byte *)ent); + } + +} + +/* +============== +WriteLevelLocals + +All pointer variables (except function pointers) must be handled specially. +============== +*/ +void WriteLevelLocals (FILE *f) +{ + field_t *field; + level_locals_t temp; + + // all of the ints, floats, and vectors stay as they are + temp = level; + + // change the pointers to lengths or indexes + for (field=levelfields ; field->name ; field++) + { + WriteField1 (f, field, (byte *)&temp); + } + + // write the block + fwrite (&temp, sizeof(temp), 1, f); + + // now write any allocated data following the edict + for (field=levelfields ; field->name ; field++) + { + WriteField2 (f, field, (byte *)&level); + } +} + + +/* +============== +ReadEdict + +All pointer variables (except function pointers) must be handled specially. +============== +*/ +void ReadEdict (FILE *f, edict_t *ent) +{ + field_t *field; + + fread (ent, sizeof(*ent), 1, f); + + for (field=savefields ; field->name ; field++) + { + ReadField (f, field, (byte *)ent); + } +} + +/* +============== +ReadLevelLocals + +All pointer variables (except function pointers) must be handled specially. +============== +*/ +void ReadLevelLocals (FILE *f) +{ + field_t *field; + + fread (&level, sizeof(level), 1, f); + + for (field=levelfields ; field->name ; field++) + { + ReadField (f, field, (byte *)&level); + } +} + +/* +================= +WriteLevel + +================= +*/ +void WriteLevel (char *filename) +{ + int i; + edict_t *ent; + FILE *f; + void *base; + + f = fopen (filename, "wb"); + if (!f) + gi.error ("Couldn't open %s", filename); + + // write out edict size for checking + i = sizeof(edict_t); + fwrite (&i, sizeof(i), 1, f); + + // write out a function pointer for checking + base = (void *)InitGame; + fwrite (&base, sizeof(base), 1, f); + + // write out level_locals_t + WriteLevelLocals (f); + + // write out all the entities + for (i=0 ; iinuse) + continue; + fwrite (&i, sizeof(i), 1, f); + WriteEdict (f, ent); + } + i = -1; + fwrite (&i, sizeof(i), 1, f); + + fclose (f); +} + + +/* +================= +ReadLevel + +SpawnEntities will already have been called on the +level the same way it was when the level was saved. + +That is necessary to get the baselines +set up identically. + +The server will have cleared all of the world links before +calling ReadLevel. + +No clients are connected yet. +================= +*/ +void ReadLevel (char *filename) +{ + int entnum; + FILE *f; + int i; + void *base; + edict_t *ent; + + f = fopen (filename, "rb"); + if (!f) + gi.error ("Couldn't open %s", filename); + + // free any dynamic memory allocated by loading the level + // base state + gi.FreeTags (TAG_LEVEL); + + // wipe all the entities + memset (g_edicts, 0, game.maxentities*sizeof(g_edicts[0])); + globals.num_edicts = maxclients->value+1; + + // check edict size + fread (&i, sizeof(i), 1, f); + if (i != sizeof(edict_t)) + { + fclose (f); + gi.error ("ReadLevel: mismatched edict size"); + } + + // check function pointer base address + fread (&base, sizeof(base), 1, f); + if (base != (void *)InitGame) + { + fclose (f); + gi.error ("ReadLevel: function pointers have moved"); + } + + // load the level locals + ReadLevelLocals (f); + + // load all the entities + while (1) + { + if (fread (&entnum, sizeof(entnum), 1, f) != 1) + { + fclose (f); + gi.error ("ReadLevel: failed to read entnum"); + } + if (entnum == -1) + break; + if (entnum >= globals.num_edicts) + globals.num_edicts = entnum+1; + + ent = &g_edicts[entnum]; + ReadEdict (f, ent); + + // let the server rebuild world links for this ent + memset (&ent->area, 0, sizeof(ent->area)); + gi.linkentity (ent); + } + + fclose (f); + + // mark all clients as unconnected + for (i=0 ; ivalue ; i++) + { + ent = &g_edicts[i+1]; + ent->client = game.clients + i; + ent->client->pers.connected = false; + } + + // do any load time things at this point + for (i=0 ; iinuse) + continue; + + // fire any cross-level triggers + if (ent->classname) + if (strcmp(ent->classname, "target_crosslevel_target") == 0) + ent->nextthink = level.time + ent->delay; + } +} diff --git a/src/g_spawn.c b/src/g_spawn.c new file mode 100644 index 0000000..39be9aa --- /dev/null +++ b/src/g_spawn.c @@ -0,0 +1,1474 @@ + +#include "g_local.h" +#include "bot.h" +typedef struct +{ + char *name; + void (*spawn)(edict_t *ent); +} spawn_t; + + +void SP_item_health (edict_t *self); +void SP_item_health_small (edict_t *self); +void SP_item_health_large (edict_t *self); +void SP_item_health_mega (edict_t *self); + +void SP_info_player_start (edict_t *ent); +void SP_info_player_deathmatch (edict_t *ent); +void SP_info_player_coop (edict_t *ent); +void SP_info_player_intermission (edict_t *ent); + +void SP_func_plat (edict_t *ent); +void SP_func_rotating (edict_t *ent); +void SP_func_button (edict_t *ent); +void SP_func_door (edict_t *ent); +void SP_func_door_secret (edict_t *ent); +void SP_func_door_rotating (edict_t *ent); +void SP_func_water (edict_t *ent); +void SP_func_train (edict_t *ent); +void SP_func_conveyor (edict_t *self); +void SP_func_wall (edict_t *self); +void SP_func_object (edict_t *self); +void SP_func_explosive (edict_t *self); +void SP_func_timer (edict_t *self); +void SP_func_areaportal (edict_t *ent); +void SP_func_clock (edict_t *ent); +void SP_func_killbox (edict_t *ent); + +void SP_trigger_always (edict_t *ent); +void SP_trigger_once (edict_t *ent); +void SP_trigger_multiple (edict_t *ent); +void SP_trigger_relay (edict_t *ent); +void SP_trigger_push (edict_t *ent); +void SP_trigger_hurt (edict_t *ent); +void SP_trigger_key (edict_t *ent); +void SP_trigger_counter (edict_t *ent); +void SP_trigger_elevator (edict_t *ent); +void SP_trigger_gravity (edict_t *ent); +void SP_trigger_monsterjump (edict_t *ent); + +void SP_target_temp_entity (edict_t *ent); +void SP_target_speaker (edict_t *ent); +void SP_target_explosion (edict_t *ent); +void SP_target_changelevel (edict_t *ent); +void SP_target_secret (edict_t *ent); +void SP_target_goal (edict_t *ent); +void SP_target_splash (edict_t *ent); +void SP_target_spawner (edict_t *ent); +void SP_target_blaster (edict_t *ent); +void SP_target_crosslevel_trigger (edict_t *ent); +void SP_target_crosslevel_target (edict_t *ent); +void SP_target_laser (edict_t *self); +void SP_target_help (edict_t *ent); +void SP_target_actor (edict_t *ent); +void SP_target_lightramp (edict_t *self); +void SP_target_earthquake (edict_t *ent); +void SP_target_character (edict_t *ent); +void SP_target_string (edict_t *ent); + +void SP_worldspawn (edict_t *ent); +void SP_viewthing (edict_t *ent); + +void SP_light (edict_t *self); +void SP_light_mine1 (edict_t *ent); +void SP_light_mine2 (edict_t *ent); +void SP_info_null (edict_t *self); +void SP_info_notnull (edict_t *self); +void SP_path_corner (edict_t *self); +void SP_point_combat (edict_t *self); + +void SP_misc_explobox (edict_t *self); +void SP_misc_banner (edict_t *self); +void SP_misc_satellite_dish (edict_t *self); +void SP_misc_actor (edict_t *self); +void SP_misc_gib_arm (edict_t *self); +void SP_misc_gib_leg (edict_t *self); +void SP_misc_gib_head (edict_t *self); +void SP_misc_insane (edict_t *self); +void SP_misc_deadsoldier (edict_t *self); +void SP_misc_viper (edict_t *self); +void SP_misc_viper_bomb (edict_t *self); +void SP_misc_bigviper (edict_t *self); +void SP_misc_strogg_ship (edict_t *self); +void SP_misc_teleporter (edict_t *self); +void SP_misc_teleporter_dest (edict_t *self); +void SP_misc_blackhole (edict_t *self); +void SP_misc_eastertank (edict_t *self); +void SP_misc_easterchick (edict_t *self); +void SP_misc_easterchick2 (edict_t *self); + +void SP_monster_berserk (edict_t *self); +void SP_monster_gladiator (edict_t *self); +void SP_monster_gunner (edict_t *self); +void SP_monster_infantry (edict_t *self); +void SP_monster_soldier_light (edict_t *self); +void SP_monster_soldier (edict_t *self); +void SP_monster_soldier_ss (edict_t *self); +void SP_monster_tank (edict_t *self); +void SP_monster_medic (edict_t *self); +void SP_monster_flipper (edict_t *self); +void SP_monster_chick (edict_t *self); +void SP_monster_parasite (edict_t *self); +void SP_monster_flyer (edict_t *self); +void SP_monster_brain (edict_t *self); +void SP_monster_floater (edict_t *self); +void SP_monster_hover (edict_t *self); +void SP_monster_mutant (edict_t *self); +void SP_monster_supertank (edict_t *self); +void SP_monster_boss2 (edict_t *self); +void SP_monster_jorg (edict_t *self); +void SP_monster_boss3_stand (edict_t *self); + +void SP_monster_commander_body (edict_t *self); + +void SP_turret_breach (edict_t *self); +void SP_turret_base (edict_t *self); +void SP_turret_driver (edict_t *self); + +// RAFAEL 14-APR-98 +void SP_monster_soldier_hypergun (edict_t *self); +void SP_monster_soldier_lasergun (edict_t *self); +void SP_monster_soldier_ripper (edict_t *self); +void SP_monster_fixbot (edict_t *self); +void SP_monster_gekk (edict_t *self); +void SP_monster_chick_heat (edict_t *self); +void SP_monster_gladb (edict_t *self); +void SP_monster_boss5 (edict_t *self); +void SP_rotating_light (edict_t *self); +void SP_object_repair (edict_t *self); +void SP_misc_crashviper (edict_t *ent); +void SP_misc_viper_missile (edict_t *self); +void SP_misc_amb4 (edict_t *ent); +void SP_target_mal_laser (edict_t *ent); +void SP_misc_transport (edict_t *ent); +// END 14-APR-98 + +void SP_misc_nuke (edict_t *ent); + +spawn_t spawns[] = { + {"item_health", SP_item_health}, + {"item_health_small", SP_item_health_small}, + {"item_health_large", SP_item_health_large}, + {"item_health_mega", SP_item_health_mega}, + + {"info_player_start", SP_info_player_start}, + {"info_player_deathmatch", SP_info_player_deathmatch}, + {"info_player_coop", SP_info_player_coop}, + {"info_player_intermission", SP_info_player_intermission}, +//ZOID + {"info_player_team1", SP_info_player_team1}, + {"info_player_team2", SP_info_player_team2}, +//ZOID + + {"func_plat", SP_func_plat}, + {"func_button", SP_func_button}, + {"func_door", SP_func_door}, + {"func_door_secret", SP_func_door_secret}, + {"func_door_rotating", SP_func_door_rotating}, + {"func_rotating", SP_func_rotating}, + {"func_train", SP_func_train}, + {"func_water", SP_func_water}, + {"func_conveyor", SP_func_conveyor}, + {"func_areaportal", SP_func_areaportal}, + {"func_clock", SP_func_clock}, + {"func_wall", SP_func_wall}, + {"func_object", SP_func_object}, + {"func_timer", SP_func_timer}, + {"func_explosive", SP_func_explosive}, + {"func_killbox", SP_func_killbox}, + + // RAFAEL + {"func_object_repair", SP_object_repair}, + {"rotating_light", SP_rotating_light}, + + {"trigger_always", SP_trigger_always}, + {"trigger_once", SP_trigger_once}, + {"trigger_multiple", SP_trigger_multiple}, + {"trigger_relay", SP_trigger_relay}, + {"trigger_push", SP_trigger_push}, + {"trigger_hurt", SP_trigger_hurt}, + {"trigger_key", SP_trigger_key}, + {"trigger_counter", SP_trigger_counter}, + {"trigger_elevator", SP_trigger_elevator}, + {"trigger_gravity", SP_trigger_gravity}, + {"trigger_monsterjump", SP_trigger_monsterjump}, + + {"target_temp_entity", SP_target_temp_entity}, + {"target_speaker", SP_target_speaker}, + {"target_explosion", SP_target_explosion}, + {"target_changelevel", SP_target_changelevel}, + {"target_secret", SP_target_secret}, + {"target_goal", SP_target_goal}, + {"target_splash", SP_target_splash}, + {"target_spawner", SP_target_spawner}, + {"target_blaster", SP_target_blaster}, + {"target_crosslevel_trigger", SP_target_crosslevel_trigger}, + {"target_crosslevel_target", SP_target_crosslevel_target}, + {"target_laser", SP_target_laser}, + {"target_help", SP_target_help}, +#if 0 // remove monster code + {"target_actor", SP_target_actor}, +#endif + {"target_lightramp", SP_target_lightramp}, + {"target_earthquake", SP_target_earthquake}, + {"target_character", SP_target_character}, + {"target_string", SP_target_string}, + + // RAFAEL 15-APR-98 + {"target_mal_laser", SP_target_mal_laser}, + + {"worldspawn", SP_worldspawn}, + {"viewthing", SP_viewthing}, + + {"light", SP_light}, + {"light_mine1", SP_light_mine1}, + {"light_mine2", SP_light_mine2}, + {"info_null", SP_info_null}, + {"func_group", SP_info_null}, + {"info_notnull", SP_info_notnull}, + {"path_corner", SP_path_corner}, + {"point_combat", SP_point_combat}, + + {"misc_explobox", SP_misc_explobox}, + {"misc_banner", SP_misc_banner}, +//ZOID + {"misc_ctf_banner", SP_misc_ctf_banner}, + {"misc_ctf_small_banner", SP_misc_ctf_small_banner}, +//ZOID + {"misc_satellite_dish", SP_misc_satellite_dish}, +#if 0 // remove monster code + {"misc_actor", SP_misc_actor}, +#endif + {"misc_gib_arm", SP_misc_gib_arm}, + {"misc_gib_leg", SP_misc_gib_leg}, + {"misc_gib_head", SP_misc_gib_head}, +#if 0 // remove monster code + {"misc_insane", SP_misc_insane}, +#endif + {"misc_deadsoldier", SP_misc_deadsoldier}, + {"misc_viper", SP_misc_viper}, + {"misc_viper_bomb", SP_misc_viper_bomb}, + {"misc_bigviper", SP_misc_bigviper}, + {"misc_strogg_ship", SP_misc_strogg_ship}, + {"misc_teleporter", SP_misc_teleporter}, + {"misc_teleporter_dest", SP_misc_teleporter_dest}, +//ZOID + {"trigger_teleport", SP_trigger_teleport}, + {"info_teleport_destination", SP_info_teleport_destination}, +//ZOID + {"misc_blackhole", SP_misc_blackhole}, + {"misc_eastertank", SP_misc_eastertank}, + {"misc_easterchick", SP_misc_easterchick}, + {"misc_easterchick2", SP_misc_easterchick2}, + + // RAFAEL +// {"misc_crashviper", SP_misc_crashviper}, +// {"misc_viper_missile", SP_misc_viper_missile}, + {"misc_amb4", SP_misc_amb4}, + // RAFAEL 17-APR-98 + {"misc_transport", SP_misc_transport}, + // END 17-APR-98 + // RAFAEL 12-MAY-98 + {"misc_nuke", SP_misc_nuke}, + +#if 0 // remove monster code + {"monster_berserk", SP_monster_berserk}, + {"monster_gladiator", SP_monster_gladiator}, + {"monster_gunner", SP_monster_gunner}, + {"monster_infantry", SP_monster_infantry}, + {"monster_soldier_light", SP_monster_soldier_light}, + {"monster_soldier", SP_monster_soldier}, + {"monster_soldier_ss", SP_monster_soldier_ss}, + {"monster_tank", SP_monster_tank}, + {"monster_tank_commander", SP_monster_tank}, + {"monster_medic", SP_monster_medic}, + {"monster_flipper", SP_monster_flipper}, + {"monster_chick", SP_monster_chick}, + {"monster_parasite", SP_monster_parasite}, + {"monster_flyer", SP_monster_flyer}, + {"monster_brain", SP_monster_brain}, + {"monster_floater", SP_monster_floater}, + {"monster_hover", SP_monster_hover}, + {"monster_mutant", SP_monster_mutant}, + {"monster_supertank", SP_monster_supertank}, + {"monster_boss2", SP_monster_boss2}, + {"monster_boss3_stand", SP_monster_boss3_stand}, + {"monster_jorg", SP_monster_jorg}, + + {"monster_commander_body", SP_monster_commander_body}, + + {"turret_breach", SP_turret_breach}, + {"turret_base", SP_turret_base}, + {"turret_driver", SP_turret_driver}, +#endif + + {NULL, NULL} +}; + +/* +=============== +ED_CallSpawn + +Finds the spawn function for the entity and calls it +=============== +*/ +void ED_CallSpawn (edict_t *ent) +{ + spawn_t *s; + gitem_t *item; + int i; + + if (!ent->classname) + { + gi.dprintf ("ED_CallSpawn: NULL classname\n"); + return; + } + + // check item spawn functions + for (i=0,item=itemlist ; iclassname) + continue; + if (!strcmp(item->classname, ent->classname)) + { // found it + SpawnItem (ent, item); +//ponko + if (!strcmp(ent->classname,"weapon_shotgun")) mpindex[WEAP_SHOTGUN] = i; + else if(!strcmp(ent->classname,"weapon_supershotgun")) mpindex[WEAP_SUPERSHOTGUN] = i; + else if(!strcmp(ent->classname,"weapon_machinegun")) mpindex[WEAP_MACHINEGUN] = i; + else if(!strcmp(ent->classname,"weapon_chaingun")) mpindex[WEAP_CHAINGUN] = i; + else if(!strcmp(ent->classname,"ammo_grenades")) mpindex[WEAP_GRENADES] = i; + else if(!strcmp(ent->classname,"weapon_grenadelauncher")) mpindex[WEAP_GRENADELAUNCHER] = i; + else if(!strcmp(ent->classname,"weapon_rocketlauncher")) mpindex[WEAP_ROCKETLAUNCHER] = i; + else if(!strcmp(ent->classname,"weapon_hyperblaster")) mpindex[WEAP_HYPERBLASTER] = i; + else if(!strcmp(ent->classname,"weapon_boomer")) mpindex[WEAP_BOOMER] = i; + else if(!strcmp(ent->classname,"weapon_railgun")) mpindex[WEAP_RAILGUN] = i; + else if(!strcmp(ent->classname,"weapon_phalanx")) mpindex[WEAP_PHALANX] = i; + else if(!strcmp(ent->classname,"weapon_bfg")) mpindex[WEAP_BFG] = i; + else if(!strcmp(ent->classname,"item_quad")) mpindex[MPI_QUAD] = i; + else if(!strcmp(ent->classname,"item_invulnerability")) mpindex[MPI_PENTA] = i; + else if(!strcmp(ent->classname,"item_quadfire")) mpindex[MPI_QUADF] = i; +//ponko + return; + } + } + + // check normal spawn functions + for (s=spawns ; s->name ; s++) + { + if (!strcmp(s->name, ent->classname)) + { // found it + s->spawn (ent); + return; + } + } +// gi.dprintf ("%s doesn't have a spawn function\n", ent->classname); +} + +/* +============= +ED_NewString +============= +*/ +char *ED_NewString (char *string) +{ + char *newb, *new_p; + int i,l; + + l = strlen(string) + 1; + + newb = gi.TagMalloc (l, TAG_LEVEL); + + new_p = newb; + + for (i=0 ; i< l ; i++) + { + if (string[i] == '\\' && i < l-1) + { + i++; + if (string[i] == 'n') + *new_p++ = '\n'; + else + *new_p++ = '\\'; + } + else + *new_p++ = string[i]; + } + + return newb; +} + + + + +/* +=============== +ED_ParseField + +Takes a key/value pair and sets the binary values +in an edict +=============== +*/ +void ED_ParseField (char *key, char *value, edict_t *ent) +{ + field_t *f; + byte *b; + float v; + vec3_t vec; + + for (f=fields ; f->name ; f++) + { + if (!Q_stricmp(f->name, key)) + { // found it + if (f->flags & FFL_SPAWNTEMP) + b = (byte *)&st; + else + b = (byte *)ent; + + switch (f->type) + { + case F_LSTRING: + *(char **)(b+f->ofs) = ED_NewString (value); + break; + case F_VECTOR: + sscanf (value, "%f %f %f", &vec[0], &vec[1], &vec[2]); + ((float *)(b+f->ofs))[0] = vec[0]; + ((float *)(b+f->ofs))[1] = vec[1]; + ((float *)(b+f->ofs))[2] = vec[2]; + break; + case F_INT: + *(int *)(b+f->ofs) = atoi(value); + break; + case F_FLOAT: + *(float *)(b+f->ofs) = atof(value); + break; + case F_ANGLEHACK: + v = atof(value); + ((float *)(b+f->ofs))[0] = 0; + ((float *)(b+f->ofs))[1] = v; + ((float *)(b+f->ofs))[2] = 0; + break; + case F_IGNORE: + break; + } + return; + } + } +// gi.dprintf ("%s is not a field %s\n", key,ent->classname); +} + +/* +==================== +ED_ParseEdict + +Parses an edict out of the given string, returning the new position +ed should be a properly initialized empty edict. +==================== +*/ +char *ED_ParseEdict (char *data, edict_t *ent) +{ + qboolean init; + char keyname[256]; + char *com_token; + + init = false; + memset (&st, 0, sizeof(st)); + +// go through all the dictionary pairs + while (1) + { + // parse key + com_token = COM_Parse (&data); + if (com_token[0] == '}') + break; + if (!data) + gi.error ("ED_ParseEntity: EOF without closing brace"); + + strncpy (keyname, com_token, sizeof(keyname)-1); + + // parse value + com_token = COM_Parse (&data); + if (!data) + gi.error ("ED_ParseEntity: EOF without closing brace"); + + if (com_token[0] == '}') + gi.error ("ED_ParseEntity: closing brace without data"); + + init = true; + + // keynames with a leading underscore are used for utility comments, + // and are immediately discarded by quake + if (keyname[0] == '_') + continue; + + ED_ParseField (keyname, com_token, ent); + } + + if (!init) + memset (ent, 0, sizeof(*ent)); + + return data; +} + + +/* +================ +G_FindTeams + +Chain together all entities with a matching team field. + +All but the first will have the FL_TEAMSLAVE flag set. +All but the last will have the teamchain field set to the next one +================ +*/ +void G_FindTeams (void) +{ + edict_t *e, *e2, *chain; + int i, j; + int c, c2; + + c = 0; + c2 = 0; + for (i=1, e=g_edicts+i ; i < globals.num_edicts ; i++,e++) + { + if (!e->inuse) + continue; + if (!e->team) + continue; + if (e->flags & FL_TEAMSLAVE) + continue; + chain = e; + e->teammaster = e; + c++; + c2++; + for (j=i+1, e2=e+1 ; j < globals.num_edicts ; j++,e2++) + { + if (!e2->inuse) + continue; + if (!e2->team) + continue; + if (e2->flags & FL_TEAMSLAVE) + continue; + if (!strcmp(e->team, e2->team)) + { + c2++; + chain->teamchain = e2; + e2->teammaster = e; + chain = e2; + e2->flags |= FL_TEAMSLAVE; + } + } + } + + gi.dprintf ("%i teams with %i entities\n", c, c2); +} + + +/* +================ +G_FindTrainTeam + +Chain together all entities with a matching team field. + +All but the last will have the teamchain field set to the next one +================ +*/ +void G_FindTrainTeam() +{ + edict_t *teamlist[MAX_EDICTS + 1]; + edict_t *e,*t,*p; + + qboolean findteam; + char *currtarget,*currtargetname; + char *targethist[MAX_EDICTS]; + int lc,i,j,k; + int loopindex; + + e = &g_edicts[(int)maxclients->value+1]; + for ( i=maxclients->value+1 ; iinuse && e->classname) + { + if(!Q_stricmp(e->classname,"path_corner") && e->targetname && e->target) + { +// orgtarget = e->target; //terminal +// orgtargetname = e->targetname; + currtarget = e->target; //target + currtargetname = e->targetname; //targetname + lc = 0; + + memset(&teamlist,0,sizeof(teamlist)); + memset(&targethist,0,sizeof(targethist)); + findteam = false; + + loopindex = 0; + targethist[0] = e->targetname; + + while(lc < MAX_EDICTS) + { + t = &g_edicts[(int)maxclients->value+1]; + for ( j=maxclients->value+1 ; jinuse && t->classname) + { + if(!Q_stricmp(t->classname,"func_train") + && !Q_stricmp(t->target,currtargetname) + && t->trainteam == NULL) + { + for(k = 0;k < lc; k++) + { + if(teamlist[k] == t) break; + } + if(k == lc ) + { + teamlist[lc] = t; + lc++; + } + } + } + } + p = G_PickTarget(currtarget); + if(!p) break; + currtarget = p->target; + currtargetname = p->targetname; + if(!p->target) break; + for(k = 0;k < loopindex;k++) + { + if(!Q_stricmp(targethist[k],currtargetname)) break; + } + if(k < loopindex) + { + findteam = true; + break; + } + targethist[loopindex] = currtargetname; + loopindex++; + /*if(!Q_stricmp(currtarget,orgtargetname)) + { + findteam = true; + break; + }*/ + } + if(findteam && lc > 0) + { + gi.dprintf("%i train chaining founded.\n",lc); + for(k = 0;k < lc; k++) + { + if(teamlist[k + 1] == NULL) + { + teamlist[k]->trainteam = teamlist[0]; + break; + } + teamlist[k]->trainteam = teamlist[k + 1]; + } + } + } + } + } +} +/* +================ +G_FindItemLink + + + + +================ +*/ + + +void G_FindItemLink() +{ + int i,j,k; + + if(CurrentIndex <= 0) return; + + //search + for(i = 0;i < CurrentIndex;i++) + { + if(Route[i].state == GRS_ITEMS) + { + for(j = 0;j < CurrentIndex;j++) + { + //found!! + if(j != i && Route[i].ent == Route[j].ent) + { + for(k = 0;k < (MAXLINKPOD - (ctf->value != 0));k++) + { + //search blanked index + if(!Route[i].linkpod[k]) + { + Route[i].linkpod[k] = j; + break; + } + } + } + } + } + } +} + + +/* +================ +RTJump_Chk +================ +*/ + +qboolean RTJump_Chk(vec3_t apos,vec3_t tpos) +{ + float x,l,grav,vel,ypos,yori; + vec3_t v,vv; + int mf = false; + + grav = 1.0 * sv_gravity->value * FRAMETIME; + + vel = VEL_BOT_JUMP; + yori = apos[2]; + ypos = tpos[2]; + + VectorSubtract(tpos,apos,v); + + for(x = 1;x <= FALLCHK_LOOPMAX * 2 ;++x ) + { + vel -= grav; + yori += vel * FRAMETIME; + + if(vel > 0) + { + if(mf == false) + { + if(ypos < yori) mf = 2; + } + } + else if(x > 1) + { + if(mf == false) + { + if(ypos < yori) mf = 2; + } + + else if(mf == 2) + { + if(ypos >= yori) + { + mf = true; + break; + } + } + } + } + VectorCopy(v,vv); + vv[2] = 0; + + l = VectorLength(vv); + + if(x > 1) l = l / (x - 1); + if(l < MOVE_SPD_RUN && mf == true) + { + return true; + } + return false; +} + +/* +================ +G_FindRouteLink +================ +*/ +void G_FindRouteLink(edict_t *ent) +{ + trace_t rs_trace; + + gitem_t *item; + qboolean tpbool; + + int i,j,k,l,total = 0; + vec3_t v,vv; + float x; + + + //旗を発生させる + if(!ctf->value && zigmode->value == 1) + { + item = FindItem("Zig Flag"); + SelectSpawnPoint (ent, v, vv); + // VectorCopy (v, ent->s.origin); + if(ZIGDrop_Flag(ent,zflag_item)) + { + VectorCopy (v, zflag_ent->s.origin); + } + //ZIGDrop_Flag(ent,item); + zigflag_spawn = 2; + } +gi.dprintf("Linking routes...\n"); + + //get JumpMax + if(JumpMax == 0) + { + x = VEL_BOT_JUMP - ent->gravity * sv_gravity->value * FRAMETIME; + JumpMax = 0; + while(1) + { + JumpMax += x * FRAMETIME; + x -= ent->gravity * sv_gravity->value * FRAMETIME; + if( x < 0 ) break; + } + } + + //search + for(i = 0;i < CurrentIndex;i++) + { + if(Route[i].state == GRS_NORMAL) + { + for(j = 0;j < CurrentIndex;j++) + { + if(abs(i - j) <= 50 || j == i || Route[j].state != GRS_NORMAL) continue; + + VectorSubtract(Route[j].Pt,Route[i].Pt,v); + if(v[2] > JumpMax || v[2] < -500) continue; + v[2] = 0; + if(VectorLength(v) > 200) continue; + + if(fabs(v[2]) > 20 || VectorLength(v) > 64) + { + if(!RTJump_Chk(Route[i].Pt,Route[j].Pt)) + continue; + } + + tpbool = false; + for(l = -5;l < 6;l++) + { + if( (i + l) < 0 || (i + l) >= CurrentIndex) continue; + for(k = 0;k < (MAXLINKPOD - (ctf->value != 0));k++) + { + //search blanked index + if(!Route[i + l].linkpod[k]) break; + if(abs(Route[i + l].linkpod[k] - j) < 50) + { + tpbool = true; + break; + } + } + if(tpbool) break; + } + if(tpbool) continue; + +// VectorSubtract(Route[j].Pt,Route[i].Pt,v); + + rs_trace = gi.trace(Route[j].Pt,NULL,NULL,Route[i].Pt,ent,MASK_SOLID); + //found!! + if(!rs_trace.startsolid && !rs_trace.allsolid && rs_trace.fraction == 1.0) + { + for(k = 0;k < (MAXLINKPOD - (ctf->value != 0));k++) + { + //search blanked index + if(!Route[i].linkpod[k]) + { + Route[i].linkpod[k] = j; + total++; + break; + } + } + } + } + } + } +//gi.dprintf("yare!!!!!!!\n"); + if(ctf->value && bot_team_flag1 && bot_team_flag2) + { + j = 0; + k = 0; + //search + for(i = (CurrentIndex - 1);i >= 0 ;i--) + { + if(Route[i].state < GRS_ITEMS) + { + if(Route[i].state == GRS_REDFLAG || Route[i].state == GRS_BLUEFLAG) + { + if(Route[i].ent == bot_team_flag1){ j = FOR_FLAG1; k = i;} + else if(Route[i].ent == bot_team_flag2) {j = FOR_FLAG2;k = i;} + } + + if(j == FOR_FLAG1) Route[i].linkpod[MAXLINKPOD - 1] = (CTF_FLAG1_FLAG | k); + else if(j == FOR_FLAG2) Route[i].linkpod[MAXLINKPOD - 1] = (CTF_FLAG2_FLAG | k); + else Route[i].linkpod[MAXLINKPOD - 1] = 0; + } + } + } + +gi.dprintf("Total %i linking done.\n",total); + G_FreeEdict (ent); +} + +/* +================ +G_SpawnRouteLink +================ +*/ +void G_SpawnRouteLink() +{ + edict_t *e; + + if(CurrentIndex <= 0) return; + + e = G_Spawn(); + + e->nextthink = level.time + FRAMETIME * 2; + e->think = G_FindRouteLink; +} + +/* +============== +SpawnEntities + +Creates a server's entity / program execution context by +parsing textual entity definitions out of an ent file. +============== +*/ +//void SetBotFlag1(edict_t *ent); //チーム1の旗 +//void SetBotFlag2(edict_t *ent); //チーム2の旗 + +//void CTFSetupNavSpawn(); //ナビの設置 +void SpawnEntities (char *mapname, char *entities, char *spawnpoint) +{ + edict_t *ent; + int inhibit; + char *com_token; + int i; + float skill_level; + + int laser = 0; +//ponko + memset(mpindex,0,sizeof(mpindex)); //target item index + memset(LaserIndex,0,sizeof(LaserIndex)); +//ponko + + skill_level = floor (skill->value); + if (skill_level < 0) + skill_level = 0; + if (skill_level > 4) + skill_level = 4; + if (skill->value != skill_level) + gi.cvar_forceset("skill", va("%f", skill_level)); + + SaveClientData (); + + gi.FreeTags (TAG_LEVEL); + + memset (&level, 0, sizeof(level)); + memset (g_edicts, 0, game.maxentities * sizeof (g_edicts[0])); + + strncpy (level.mapname, mapname, sizeof(level.mapname)-1); + strncpy (game.spawnpoint, spawnpoint, sizeof(game.spawnpoint)-1); + + // set client fields on player ents + for (i=0 ; ivalue) + { + if ( ent->spawnflags & SPAWNFLAG_NOT_DEATHMATCH ) + { + G_FreeEdict (ent); + inhibit++; + continue; + } + } + else + { + if ( /* ((coop->value) && (ent->spawnflags & SPAWNFLAG_NOT_COOP)) || */ + ((skill->value == 0) && (ent->spawnflags & SPAWNFLAG_NOT_EASY)) || + ((skill->value == 1) && (ent->spawnflags & SPAWNFLAG_NOT_MEDIUM)) || + (((skill->value == 2) || (skill->value == 3)) && (ent->spawnflags & SPAWNFLAG_NOT_HARD)) + ) + { + G_FreeEdict (ent); + inhibit++; + continue; + } + } + + ent->spawnflags &= ~(SPAWNFLAG_NOT_EASY|SPAWNFLAG_NOT_MEDIUM|SPAWNFLAG_NOT_HARD|SPAWNFLAG_NOT_COOP|SPAWNFLAG_NOT_DEATHMATCH); + } + + ED_CallSpawn (ent); +//laser index + if (Q_stricmp(ent->classname, "target_laser") == 0) + { + if(laser < MAX_LASERINDEX) LaserIndex[laser++] = ent; + } +//PON-CTF + if(ent->solid == SOLID_TRIGGER && ctf->value && chedit->value) ent->moveinfo.speed = 0; + if (Q_stricmp(ent->classname, "item_flag_team1") == 0) bot_team_flag1 = ent; + else if (Q_stricmp(ent->classname, "item_flag_team2") == 0) bot_team_flag2 = ent; +//PON-CTF + } + + gi.dprintf ("%i entities inhibited\n", inhibit); + + G_FindTeams (); + + PlayerTrail_Init (); + + //func_trainのリンク + G_FindTrainTeam(); + + +//ZOID + CTFSetupTechSpawn(); + +//ZOID + +//ponko + CTFSetupNavSpawn(); //ナビの設置 + if(!chedit->value) G_FindItemLink(); //アイテムのリンク(通常時のみ) + + G_SpawnRouteLink(); + + if(zigmode->value == 1) zigflag_spawn = 1; + else zigflag_spawn = 0; + //旗のアイテムアドレス取得 + zflag_item = FindItem("Zig Flag"); + zflag_ent = NULL; //初期化 +// if(CurrentIndex > 0) +//ponko + + ctfjob_update = level.time; +} + + +//=================================================================== + +#if 0 + // cursor positioning + xl + xr + yb + yt + xv + yv + + // drawing + statpic + pic + num + string + + // control + if + ifeq + ifbit + endif + +#endif + +char *single_statusbar = +"yb -24 " + +// health +"xv 0 " +"hnum " +"xv 50 " +"pic 0 " + +// ammo +"if 2 " +" xv 100 " +" anum " +" xv 150 " +" pic 2 " +"endif " + +// armor +"if 4 " +" xv 200 " +" rnum " +" xv 250 " +" pic 4 " +"endif " + +// selected item +"if 6 " +" xv 296 " +" pic 6 " +"endif " + +"yb -50 " + +// picked up item +"if 7 " +" xv 0 " +" pic 7 " +" xv 26 " +" yb -42 " +" stat_string 8 " +" yb -50 " +"endif " + +// timer +"if 9 " +" xv 262 " +" num 2 10 " +" xv 296 " +" pic 9 " +"endif " + +// help / weapon icon +"if 11 " +" xv 148 " +" pic 11 " +"endif " +; + +char *dm_statusbar = +"yb -24 " + +// health +"xv 0 " +"hnum " +"xv 50 " +"pic 0 " + +// ammo +"if 2 " +" xv 100 " +" anum " +" xv 150 " +" pic 2 " +"endif " + +// armor +"if 4 " +" xv 200 " +" rnum " +" xv 250 " +" pic 4 " +"endif " + +// selected item +"if 6 " +" xv 296 " +" pic 6 " +"endif " + +"yb -50 " + +// picked up item +"if 7 " +" xv 0 " +" pic 7 " +" xv 26 " +" yb -42 " +" stat_string 8 " +" yb -50 " +"endif " + +// timer +"if 9 " +" xv 246 " +" num 2 10 " +" xv 296 " +" pic 9 " +"endif " + +// help / weapon icon +"if 11 " +" xv 148 " +" pic 11 " +"endif " + +// frags +"xr -50 " +"yt 2 " +"num 3 14 " + +//sight +"if 31 " +" xv 96 " +" yv 56 " +" pic 31 " +"endif" +; + + +/*QUAKED worldspawn (0 0 0) ? + +Only used for the world. +"sky" environment map name +"skyaxis" vector axis for rotating sky +"skyrotate" speed of rotation in degrees/second +"sounds" music cd track number +"gravity" 800 is default gravity +"message" text to print at user logon +*/ +void SP_worldspawn (edict_t *ent) +{ + ent->movetype = MOVETYPE_PUSH; + ent->solid = SOLID_BSP; + ent->inuse = true; // since the world doesn't use G_Spawn() + ent->s.modelindex = 1; // world model is always index 1 + + //--------------- + + // reserve some spots for dead player bodies + InitBodyQue (); + + // set configstrings for items + SetItemNames (); + + if (st.nextmap) + strcpy (level.nextmap, st.nextmap); + + // make some data visible to the server + + if (ent->message && ent->message[0]) + { + gi.configstring (CS_NAME, ent->message); + strncpy (level.level_name, ent->message, sizeof(level.level_name)); + } + else + strncpy (level.level_name, level.mapname, sizeof(level.level_name)); + + if (st.sky && st.sky[0]) + gi.configstring (CS_SKY, st.sky); + else + gi.configstring (CS_SKY, "unit1_"); + + gi.configstring (CS_SKYROTATE, va("%f", st.skyrotate) ); + + gi.configstring (CS_SKYAXIS, va("%f %f %f", + st.skyaxis[0], st.skyaxis[1], st.skyaxis[2]) ); + + gi.configstring (CS_CDTRACK, va("%i", ent->sounds) ); + + gi.configstring (CS_MAXCLIENTS, va("%i", (int)(maxclients->value) ) ); + + // status bar program + if (deathmatch->value) +//ZOID + if (ctf->value) { + gi.configstring (CS_STATUSBAR, ctf_statusbar); + //precaches + gi.imageindex("sbfctf1"); + gi.imageindex("sbfctf2"); +// gi.imageindex("ctfsb1"); +// gi.imageindex("ctfsb2"); + gi.imageindex("i_ctf1"); + gi.imageindex("i_ctf2"); + gi.imageindex("i_ctf1d"); + gi.imageindex("i_ctf2d"); + gi.imageindex("i_ctf1t"); + gi.imageindex("i_ctf2t"); + gi.imageindex("i_ctfj"); + +/* if(ctf->value == 2) + { + gi.modelindex("models/weapons/v_hook/tris.md2"); + gi.soundindex("weapons/grapple/grhit.wav"); + gi.soundindex("weapons/grapple/grpull.wav"); + gi.soundindex("weapons/grapple/grfire.wav"); + }*/ + } else +//ZOID + gi.configstring (CS_STATUSBAR, dm_statusbar); + else + gi.configstring (CS_STATUSBAR, single_statusbar); + + //--------------- + + + // help icon for statusbar + gi.imageindex ("i_help"); + level.pic_health = gi.imageindex ("i_health"); + gi.imageindex ("help"); + gi.imageindex ("field_3"); + + if (!st.gravity) + gi.cvar_set("sv_gravity", "800"); + else + gi.cvar_set("sv_gravity", st.gravity); + + snd_fry = gi.soundindex ("player/fry.wav"); // standing in lava / slime + + PrecacheItem (FindItem ("Blaster")); + + gi.soundindex ("player/lava1.wav"); + gi.soundindex ("player/lava2.wav"); + + gi.soundindex ("misc/pc_up.wav"); + gi.soundindex ("misc/talk1.wav"); + + gi.soundindex ("misc/udeath.wav"); + + // gibs + gi.soundindex ("items/respawn1.wav"); + + // sexed sounds + gi.soundindex ("*death1.wav"); + gi.soundindex ("*death2.wav"); + gi.soundindex ("*death3.wav"); + gi.soundindex ("*death4.wav"); + gi.soundindex ("*fall1.wav"); + gi.soundindex ("*fall2.wav"); + gi.soundindex ("*gurp1.wav"); // drowning damage + gi.soundindex ("*gurp2.wav"); + gi.soundindex ("*jump1.wav"); // player jump + gi.soundindex ("*pain25_1.wav"); + gi.soundindex ("*pain25_2.wav"); + gi.soundindex ("*pain50_1.wav"); + gi.soundindex ("*pain50_2.wav"); + gi.soundindex ("*pain75_1.wav"); + gi.soundindex ("*pain75_2.wav"); + gi.soundindex ("*pain100_1.wav"); + gi.soundindex ("*pain100_2.wav"); + +/* if (coop->value || deathmatch->value) + {*/ + gi.modelindex ("#w_blaster.md2"); + gi.modelindex ("#w_shotgun.md2"); + gi.modelindex ("#w_sshotgun.md2"); + gi.modelindex ("#w_machinegun.md2"); + gi.modelindex ("#w_chaingun.md2"); + gi.modelindex ("#a_grenades.md2"); + gi.modelindex ("#w_glauncher.md2"); + gi.modelindex ("#w_rlauncher.md2"); + gi.modelindex ("#w_hyperblaster.md2"); + gi.modelindex ("#w_railgun.md2"); + gi.modelindex ("#w_bfg.md2"); + + gi.modelindex ("#w_phalanx.md2"); + gi.modelindex ("#w_ripper.md2"); +// } + + + //------------------- + + gi.soundindex ("player/gasp1.wav"); // gasping for air + gi.soundindex ("player/gasp2.wav"); // head breaking surface, not gasping + + gi.soundindex ("player/watr_in.wav"); // feet hitting water + gi.soundindex ("player/watr_out.wav"); // feet leaving water + + gi.soundindex ("player/watr_un.wav"); // head going underwater + + gi.soundindex ("player/u_breath1.wav"); + gi.soundindex ("player/u_breath2.wav"); + + gi.soundindex ("items/pkup.wav"); // bonus item pickup + gi.soundindex ("world/land.wav"); // landing thud + gi.soundindex ("misc/h2ohit1.wav"); // landing splash + + gi.soundindex ("items/damage.wav"); + gi.soundindex ("items/protect.wav"); + gi.soundindex ("items/protect4.wav"); + gi.soundindex ("weapons/noammo.wav"); + + gi.soundindex ("infantry/inflies1.wav"); + + sm_meat_index = gi.modelindex ("models/objects/gibs/sm_meat/tris.md2"); + gi.modelindex ("models/objects/gibs/arm/tris.md2"); + gi.modelindex ("models/objects/gibs/bone/tris.md2"); + gi.modelindex ("models/objects/gibs/bone2/tris.md2"); + gi.modelindex ("models/objects/gibs/chest/tris.md2"); + skullindex = gi.modelindex ("models/objects/gibs/skull/tris.md2"); + headindex = gi.modelindex ("models/objects/gibs/head2/tris.md2"); + +// +// Setup light animation tables. 'a' is total darkness, 'z' is doublebright. +// + + // 0 normal + gi.configstring(CS_LIGHTS+0, "m"); + + // 1 FLICKER (first variety) + gi.configstring(CS_LIGHTS+1, "mmnmmommommnonmmonqnmmo"); + + // 2 SLOW STRONG PULSE + gi.configstring(CS_LIGHTS+2, "abcdefghijklmnopqrstuvwxyzyxwvutsrqponmlkjihgfedcba"); + + // 3 CANDLE (first variety) + gi.configstring(CS_LIGHTS+3, "mmmmmaaaaammmmmaaaaaabcdefgabcdefg"); + + // 4 FAST STROBE + gi.configstring(CS_LIGHTS+4, "mamamamamama"); + + // 5 GENTLE PULSE 1 + gi.configstring(CS_LIGHTS+5,"jklmnopqrstuvwxyzyxwvutsrqponmlkj"); + + // 6 FLICKER (second variety) + gi.configstring(CS_LIGHTS+6, "nmonqnmomnmomomno"); + + // 7 CANDLE (second variety) + gi.configstring(CS_LIGHTS+7, "mmmaaaabcdefgmmmmaaaammmaamm"); + + // 8 CANDLE (third variety) + gi.configstring(CS_LIGHTS+8, "mmmaaammmaaammmabcdefaaaammmmabcdefmmmaaaa"); + + // 9 SLOW STROBE (fourth variety) + gi.configstring(CS_LIGHTS+9, "aaaaaaaazzzzzzzz"); + + // 10 FLUORESCENT FLICKER + gi.configstring(CS_LIGHTS+10, "mmamammmmammamamaaamammma"); + + // 11 SLOW PULSE NOT FADE TO BLACK + gi.configstring(CS_LIGHTS+11, "abcdefghijklmnopqrrqponmlkjihgfedcba"); + + // styles 32-62 are assigned by the light program for switchable lights + + // 63 testing + gi.configstring(CS_LIGHTS+63, "a"); + +//---------------------------------------------- + + + //pre searched items + Fdi_GRAPPLE = FindItem ("Grapple"); + Fdi_BLASTER = FindItem ("Blaster"); + Fdi_SHOTGUN = FindItem ("Shotgun"); + Fdi_SUPERSHOTGUN = FindItem ("Super Shotgun"); + Fdi_MACHINEGUN = FindItem ("Machinegun"); + Fdi_CHAINGUN = FindItem ("Chaingun"); + Fdi_GRENADES = FindItem ("Grenades"); + Fdi_GRENADELAUNCHER = FindItem ("Grenade Launcher"); + Fdi_ROCKETLAUNCHER = FindItem ("Rocket Launcher"); + Fdi_HYPERBLASTER = FindItem ("HyperBlaster"); + Fdi_RAILGUN = FindItem ("Railgun"); + Fdi_BFG = FindItem ("BFG10K"); + Fdi_PHALANX = FindItem ("Phalanx"); + Fdi_BOOMER = FindItem ("Ionripper"); + Fdi_TRAP = FindItem ("Trap"); + + Fdi_SHELLS = FindItem ("Shells"); + Fdi_BULLETS = FindItem ("Bullets"); + Fdi_CELLS = FindItem ("Cells"); + Fdi_ROCKETS = FindItem ("Rockets"); + Fdi_SLUGS = FindItem ("Slugs"); + Fdi_MAGSLUGS = FindItem ("Mag Slug"); + + memset(ExplIndex,0,sizeof(ExplIndex)); +} + + diff --git a/src/g_svcmds.c b/src/g_svcmds.c new file mode 100644 index 0000000..44cc609 --- /dev/null +++ b/src/g_svcmds.c @@ -0,0 +1,456 @@ + +#include "g_local.h" +#include "bot.h" + + + +/* +============================================================================== + +PACKET FILTERING + + +You can add or remove addresses from the filter list with: + +addip +removeip + +The ip address is specified in dot format, and any unspecified digits will match any value, so you can specify an entire class C network with "addip 192.246.40". + +Removeip will only remove an address specified exactly the same way. You cannot addip a subnet, then removeip a single host. + +listip +Prints the current list of filters. + +writeip +Dumps "addip " commands to listip.cfg so it can be execed at a later date. The filter lists are not saved and restored by default, because I beleive it would cause too much confusion. + +filterban <0 or 1> + +If 1 (the default), then ip addresses matching the current list will be prohibited from entering the game. This is the default setting. + +If 0, then only addresses matching the list will be allowed. This lets you easily set up a private game, or a game that only allows players from your local network. + + +============================================================================== +*/ + +typedef struct +{ + unsigned mask; + unsigned compare; +} ipfilter_t; + +#define MAX_IPFILTERS 1024 + +ipfilter_t ipfilters[MAX_IPFILTERS]; +int numipfilters; + +/* +================= +StringToFilter +================= +*/ +static qboolean StringToFilter (char *s, ipfilter_t *f) +{ + char num[128]; + int i, j; + byte b[4]; + byte m[4]; + + for (i=0 ; i<4 ; i++) + { + b[i] = 0; + m[i] = 0; + } + + for (i=0 ; i<4 ; i++) + { + if (*s < '0' || *s > '9') + { + gi.cprintf(NULL, PRINT_HIGH, "Bad filter address: %s\n", s); + return false; + } + + j = 0; + while (*s >= '0' && *s <= '9') + { + num[j++] = *s++; + } + num[j] = 0; + b[i] = atoi(num); + if (b[i] != 0) + m[i] = 255; + + if (!*s) + break; + s++; + } + + f->mask = *(unsigned *)m; + f->compare = *(unsigned *)b; + + return true; +} + +/* +================= +SV_FilterPacket +================= +*/ +qboolean SV_FilterPacket (char *from) +{ + int i; + unsigned in; + byte m[4]; + char *p; + + i = 0; + p = from; + while (*p && i < 4) { + m[i] = 0; + while (*p >= '0' && *p <= '9') { + m[i] = m[i]*10 + (*p - '0'); + p++; + } + if (!*p || *p == ':') + break; + i++, p++; + } + + in = *(unsigned *)m; + + for (i=0 ; ivalue; + + return (int)!filterban->value; +} + + +/* +================= +SV_AddIP_f +================= +*/ +void SVCmd_AddIP_f (void) +{ + int i; + + if (gi.argc() < 3) { + gi.cprintf(NULL, PRINT_HIGH, "Usage: addip \n"); + return; + } + + for (i=0 ; i\n"); + return; + } + + if (!StringToFilter (gi.argv(2), &f)) + return; + + for (i=0 ; istring) + sprintf (name, "%s/listip.cfg", GAMEVERSION); + else + sprintf (name, "%s/listip.cfg", game->string); + + gi.cprintf (NULL, PRINT_HIGH, "Writing %s.\n", name); + + f = fopen (name, "wb"); + if (!f) + { + gi.cprintf (NULL, PRINT_HIGH, "Couldn't open %s\n", name); + return; + } + + fprintf(f, "set filterban %d\n", (int)filterban->value); + + for (i=0 ; i= 0;i--) + { + if(Route[i].state) break; + else if(!Route[i].index) break; + } + if(!CurrentIndex || !Route[i].index) CurrentIndex = i; + else CurrentIndex = i + 1; + + if(CurrentIndex < MAXNODES) + { + memset(&Route[CurrentIndex],0,sizeof(route_t)); + if(CurrentIndex > 0) Route[CurrentIndex].index = Route[CurrentIndex - 1].index + 1; + } +} + +//分岐付きに変換処理 +void RouteTreepointSet() +{ + int i; + + for(i = 0;i < CurrentIndex;i++) + { + if(Route[i].state == GRS_NORMAL) + { + + + } + } +} + + + + +void Svcmd_Test_f (void) +{ + gi.cprintf (NULL, PRINT_HIGH, "Svcmd_Test_f()\n"); +} + +//chainファイルのセーブ +void SaveChain() +{ + char name[256]; + FILE *fpout; + unsigned int size; + + if(!chedit->value) + { + gi.cprintf (NULL, PRINT_HIGH, "Not a chaining mode.\n"); + return; + } + + //とりあえずCTFだめ + if(ctf->value) sprintf(name,".\\%s\\chctf\\%s.chf",gamepath->string,level.mapname); + else sprintf(name,".\\%s\\chdtm\\%s.chn",gamepath->string,level.mapname); + + fpout = fopen(name,"wb"); + if(fpout == NULL) gi.cprintf(NULL,PRINT_HIGH,"Can't open %s\n",name); + else + { + if(!ctf->value) fwrite("3ZBRGDTM",sizeof(char),8,fpout); + else fwrite("3ZBRGCTF",sizeof(char),8,fpout); + + fwrite(&CurrentIndex,sizeof(int),1,fpout); + + size = (unsigned int)CurrentIndex * sizeof(route_t); + + fwrite(Route,size,1,fpout); + + gi.cprintf (NULL, PRINT_HIGH,"%s Saving done.\n",name); + fclose(fpout); + } +} +//Spawn Command +void SpawnCommand(int i) +{ + int j; + + if(chedit->value){ gi.cprintf(NULL,PRINT_HIGH,"Can't spawn.");return;} + + if(i <= 0) {gi.cprintf(NULL,PRINT_HIGH,"Specify num of bots.");return;} + + for(j = 0;j < i;j++) + { + SpawnBotReserving(); + } +} + +//Random Spawn Command + +void RandomSpawnCommand(int i) +{ + int j,k,red = 0,blue = 0; + + edict_t *e; + + if(chedit->value){ gi.cprintf(NULL,PRINT_HIGH,"Can't spawn.");return;} + + if(i <= 0) {gi.cprintf(NULL,PRINT_HIGH,"Specify num of bots.");return;} + + //count current teams + for ( k = 1 ; k <= maxclients->value ; k++) + { + e = &g_edicts[k]; + if(e->inuse && e->client) + { + if(e->client->resp.ctf_team == CTF_TEAM1) red++; + else if(e->client->resp.ctf_team == CTF_TEAM2) blue++; + } + } + + for(j = 0;j < i;j++) + { + SpawnBotReserving2(&red,&blue); +//gi.cprintf(NULL,PRINT_HIGH,"R B %i %i\n",red,blue); + } +} + +//Remove Command +void RemoveCommand(int i) +{ + int j; + + if(i <= 0) i = 1;//gi.cprintf(NULL,PRINT_HIGH,"Specify num of bots."); + + + for(j = 0;j < i;j++) + { + RemoveBot(); + } +} + +//Debug Spawn Command +void DebugSpawnCommand(int i) +{ + if(!chedit->value) {gi.cprintf(NULL,PRINT_HIGH,"Can't debug.");return;} + + if(targetindex) {gi.cprintf(NULL,PRINT_HIGH,"Now debugging.");return;} + + if(i < 1) i = 1; + + targetindex = i; + + SpawnBotReserving(); +} + + +/* +================= +ServerCommand + +ServerCommand will be called when an "sv" command is issued. +The game can issue gi.argc() / gi.argv() commands to get the rest +of the parameters +================= +*/ +void ServerCommand (void) +{ + char *cmd; + + cmd = gi.argv(1); + if (Q_stricmp (cmd, "test") == 0) + Svcmd_Test_f (); + else if (Q_stricmp (cmd, "savechain") == 0) + SaveChain (); + else if (Q_stricmp (cmd, "spb") == 0) + { + if(gi.argc() <= 1) SpawnCommand(1); + else SpawnCommand (atoi(gi.argv(2))); + } + else if (Q_stricmp (cmd, "rspb") == 0) + { + if(gi.argc() <= 1) RandomSpawnCommand(1); + else RandomSpawnCommand (atoi(gi.argv(2))); + } + else if (Q_stricmp (cmd, "rmb") == 0) + { + if(gi.argc() <= 1) RemoveCommand(1); + else RemoveCommand (atoi(gi.argv(2))); + } + else if (Q_stricmp (cmd, "dsp") == 0) + { + if(gi.argc() <= 1) DebugSpawnCommand(1); + else DebugSpawnCommand (atoi(gi.argv(2))); + } + else if (Q_stricmp (cmd, "addip") == 0) + SVCmd_AddIP_f (); + else if (Q_stricmp (cmd, "removeip") == 0) + SVCmd_RemoveIP_f (); + else if (Q_stricmp (cmd, "listip") == 0) + SVCmd_ListIP_f (); + else if (Q_stricmp (cmd, "writeip") == 0) + SVCmd_WriteIP_f (); + else + gi.cprintf (NULL, PRINT_HIGH, "Unknown server command \"%s\"\n", cmd); +} + diff --git a/src/g_target.c b/src/g_target.c new file mode 100644 index 0000000..3873d88 --- /dev/null +++ b/src/g_target.c @@ -0,0 +1,881 @@ +#include "g_local.h" + +/*QUAKED target_temp_entity (1 0 0) (-8 -8 -8) (8 8 8) +Fire an origin based temp entity event to the clients. +"style" type byte +*/ +void Use_Target_Tent (edict_t *ent, edict_t *other, edict_t *activator) +{ + gi.WriteByte (svc_temp_entity); + gi.WriteByte (ent->style); + gi.WritePosition (ent->s.origin); + gi.multicast (ent->s.origin, MULTICAST_PVS); +} + +void SP_target_temp_entity (edict_t *ent) +{ + ent->use = Use_Target_Tent; +} + + +//========================================================== + +//========================================================== + +/*QUAKED target_speaker (1 0 0) (-8 -8 -8) (8 8 8) looped-on looped-off reliable +"noise" wav file to play +"attenuation" +-1 = none, send to whole level +1 = normal fighting sounds +2 = idle sound level +3 = ambient sound level +"volume" 0.0 to 1.0 + +Normal sounds play each time the target is used. The reliable flag can be set for crucial voiceovers. + +Looped sounds are always atten 3 / vol 1, and the use function toggles it on/off. +Multiple identical looping sounds will just increase volume without any speed cost. +*/ +void Use_Target_Speaker (edict_t *ent, edict_t *other, edict_t *activator) +{ + int chan; + + if (ent->spawnflags & 3) + { // looping sound toggles + if (ent->s.sound) + ent->s.sound = 0; // turn it off + else + ent->s.sound = ent->noise_index; // start it + } + else + { // normal sound + if (ent->spawnflags & 4) + chan = CHAN_VOICE|CHAN_RELIABLE; + else + chan = CHAN_VOICE; + // use a positioned_sound, because this entity won't normally be + // sent to any clients because it is invisible + gi.positioned_sound (ent->s.origin, ent, chan, ent->noise_index, ent->volume, ent->attenuation, 0); + } +} + +void SP_target_speaker (edict_t *ent) +{ + char buffer[MAX_QPATH]; + + if(!st.noise) + { + gi.dprintf("target_speaker with no noise set at %s\n", vtos(ent->s.origin)); + return; + } + if (!strstr (st.noise, ".wav")) + Com_sprintf (buffer, sizeof(buffer), "%s.wav", st.noise); + else + strncpy (buffer, st.noise, sizeof(buffer)); + ent->noise_index = gi.soundindex (buffer); + + if (!ent->volume) + ent->volume = 1.0; + + if (!ent->attenuation) + ent->attenuation = 1.0; + else if (ent->attenuation == -1) // use -1 so 0 defaults to 1 + ent->attenuation = 0; + + // check for prestarted looping sound + if (ent->spawnflags & 1) + ent->s.sound = ent->noise_index; + + ent->use = Use_Target_Speaker; + + // must link the entity so we get areas and clusters so + // the server can determine who to send updates to + gi.linkentity (ent); +} + + +//========================================================== + +void Use_Target_Help (edict_t *ent, edict_t *other, edict_t *activator) +{ + if (ent->spawnflags & 1) + strncpy (game.helpmessage1, ent->message, sizeof(game.helpmessage2)-1); + else + strncpy (game.helpmessage2, ent->message, sizeof(game.helpmessage1)-1); + + game.helpchanged++; +} + +/*QUAKED target_help (1 0 1) (-16 -16 -24) (16 16 24) help1 +When fired, the "message" key becomes the current personal computer string, and the message light will be set on all clients status bars. +*/ +void SP_target_help(edict_t *ent) +{ + if (deathmatch->value) + { // auto-remove for deathmatch + G_FreeEdict (ent); + return; + } + + if (!ent->message) + { + gi.dprintf ("%s with no message at %s\n", ent->classname, vtos(ent->s.origin)); + G_FreeEdict (ent); + return; + } + ent->use = Use_Target_Help; +} + +//========================================================== + +/*QUAKED target_secret (1 0 1) (-8 -8 -8) (8 8 8) +Counts a secret found. +These are single use targets. +*/ +void use_target_secret (edict_t *ent, edict_t *other, edict_t *activator) +{ + gi.sound (ent, CHAN_VOICE, ent->noise_index, 1, ATTN_NORM, 0); + + level.found_secrets++; + + G_UseTargets (ent, activator); + G_FreeEdict (ent); +} + +void SP_target_secret (edict_t *ent) +{ + if (deathmatch->value) + { // auto-remove for deathmatch + G_FreeEdict (ent); + return; + } + + ent->use = use_target_secret; + if (!st.noise) + st.noise = "misc/secret.wav"; + ent->noise_index = gi.soundindex (st.noise); + ent->svflags = SVF_NOCLIENT; + level.total_secrets++; + // map bug hack + if (!stricmp(level.mapname, "mine3") && ent->s.origin[0] == 280 && ent->s.origin[1] == -2048 && ent->s.origin[2] == -624) + ent->message = "You have found a secret area."; +} + +//========================================================== + +/*QUAKED target_goal (1 0 1) (-8 -8 -8) (8 8 8) +Counts a goal completed. +These are single use targets. +*/ +void use_target_goal (edict_t *ent, edict_t *other, edict_t *activator) +{ + gi.sound (ent, CHAN_VOICE, ent->noise_index, 1, ATTN_NORM, 0); + + level.found_goals++; + + if (level.found_goals == level.total_goals) + gi.configstring (CS_CDTRACK, "0"); + + G_UseTargets (ent, activator); + G_FreeEdict (ent); +} + +void SP_target_goal (edict_t *ent) +{ + if (deathmatch->value) + { // auto-remove for deathmatch + G_FreeEdict (ent); + return; + } + + ent->use = use_target_goal; + if (!st.noise) + st.noise = "misc/secret.wav"; + ent->noise_index = gi.soundindex (st.noise); + ent->svflags = SVF_NOCLIENT; + level.total_goals++; +} + +//========================================================== + + +/*QUAKED target_explosion (1 0 0) (-8 -8 -8) (8 8 8) +Spawns an explosion temporary entity when used. + +"delay" wait this long before going off +"dmg" how much radius damage should be done, defaults to 0 +*/ +void target_explosion_explode (edict_t *self) +{ + float save; + + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_EXPLOSION1); + gi.WritePosition (self->s.origin); + gi.multicast (self->s.origin, MULTICAST_PHS); + + T_RadiusDamage (self, self->activator, self->dmg, NULL, self->dmg+40, MOD_EXPLOSIVE); + + save = self->delay; + self->delay = 0; + G_UseTargets (self, self->activator); + self->delay = save; +} + +void use_target_explosion (edict_t *self, edict_t *other, edict_t *activator) +{ + self->activator = activator; + + if (!self->delay) + { + target_explosion_explode (self); + return; + } + + self->think = target_explosion_explode; + self->nextthink = level.time + self->delay; +} + +void SP_target_explosion (edict_t *ent) +{ + ent->use = use_target_explosion; + ent->svflags = SVF_NOCLIENT; +} + + +//========================================================== + +/*QUAKED target_changelevel (1 0 0) (-8 -8 -8) (8 8 8) +Changes level to "map" when fired +*/ +void use_target_changelevel (edict_t *self, edict_t *other, edict_t *activator) +{ + if (level.intermissiontime) + return; // already activated + + if (!deathmatch->value && !coop->value) + { + if (g_edicts[1].health <= 0) + return; + } + + // if noexit, do a ton of damage to other + if (deathmatch->value && !( (int)dmflags->value & DF_ALLOW_EXIT) && other != world) + { + T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, 10 * other->max_health, 1000, 0, MOD_EXIT); + return; + } + + // if multiplayer, let everyone know who hit the exit + if (deathmatch->value) + { + if (activator && activator->client) + gi.bprintf (PRINT_HIGH, "%s exited the level.\n", activator->client->pers.netname); + } + + // if going to a new unit, clear cross triggers + if (strstr(self->map, "*")) + game.serverflags &= ~(SFL_CROSS_TRIGGER_MASK); + + BeginIntermission (self); +} + +void SP_target_changelevel (edict_t *ent) +{ + if (!ent->map) + { + gi.dprintf("target_changelevel with no map at %s\n", vtos(ent->s.origin)); + G_FreeEdict (ent); + return; + } + + // ugly hack because *SOMEBODY* screwed up their map + if((stricmp(level.mapname, "fact1") == 0) && (stricmp(ent->map, "fact3") == 0)) + ent->map = "fact3$secret1"; + + ent->use = use_target_changelevel; + ent->svflags = SVF_NOCLIENT; +} + + +//========================================================== + +/*QUAKED target_splash (1 0 0) (-8 -8 -8) (8 8 8) +Creates a particle splash effect when used. + +Set "sounds" to one of the following: + 1) sparks + 2) blue water + 3) brown water + 4) slime + 5) lava + 6) blood + +"count" how many pixels in the splash +"dmg" if set, does a radius damage at this location when it splashes + useful for lava/sparks +*/ + +void use_target_splash (edict_t *self, edict_t *other, edict_t *activator) +{ + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_SPLASH); + gi.WriteByte (self->count); + gi.WritePosition (self->s.origin); + gi.WriteDir (self->movedir); + gi.WriteByte (self->sounds); + gi.multicast (self->s.origin, MULTICAST_PVS); + + if (self->dmg) + T_RadiusDamage (self, activator, self->dmg, NULL, self->dmg+40, MOD_SPLASH); +} + +void SP_target_splash (edict_t *self) +{ + self->use = use_target_splash; + G_SetMovedir (self->s.angles, self->movedir); + + if (!self->count) + self->count = 32; + + self->svflags = SVF_NOCLIENT; +} + + +//========================================================== + +/*QUAKED target_spawner (1 0 0) (-8 -8 -8) (8 8 8) +Set target to the type of entity you want spawned. +Useful for spawning monsters and gibs in the factory levels. + +For monsters: + Set direction to the facing you want it to have. + +For gibs: + Set direction if you want it moving and + speed how fast it should be moving otherwise it + will just be dropped +*/ +void ED_CallSpawn (edict_t *ent); + +void use_target_spawner (edict_t *self, edict_t *other, edict_t *activator) +{ + edict_t *ent; + + ent = G_Spawn(); + ent->classname = self->target; + VectorCopy (self->s.origin, ent->s.origin); + VectorCopy (self->s.angles, ent->s.angles); + ED_CallSpawn (ent); + gi.unlinkentity (ent); + KillBox (ent); + gi.linkentity (ent); + if (self->speed) + VectorCopy (self->movedir, ent->velocity); +} + +void SP_target_spawner (edict_t *self) +{ + self->use = use_target_spawner; + self->svflags = SVF_NOCLIENT; + if (self->speed) + { + G_SetMovedir (self->s.angles, self->movedir); + VectorScale (self->movedir, self->speed, self->movedir); + } +} + +//========================================================== + +/*QUAKED target_blaster (1 0 0) (-8 -8 -8) (8 8 8) NOTRAIL NOEFFECTS +Fires a blaster bolt in the set direction when triggered. + +dmg default is 15 +speed default is 1000 +*/ + +void use_target_blaster (edict_t *self, edict_t *other, edict_t *activator) +{ + int effect; + + if (self->spawnflags & 2) + effect = 0; + else if (self->spawnflags & 1) + effect = EF_HYPERBLASTER; + else + effect = EF_BLASTER; + + fire_blaster (self, self->s.origin, self->movedir, self->dmg, self->speed, EF_BLASTER, MOD_TARGET_BLASTER); + gi.sound (self, CHAN_VOICE, self->noise_index, 1, ATTN_NORM, 0); +} + +void SP_target_blaster (edict_t *self) +{ + self->use = use_target_blaster; + G_SetMovedir (self->s.angles, self->movedir); + self->noise_index = gi.soundindex ("weapons/laser2.wav"); + + if (!self->dmg) + self->dmg = 15; + if (!self->speed) + self->speed = 1000; + + self->svflags = SVF_NOCLIENT; +} + + +//========================================================== + +/*QUAKED target_crosslevel_trigger (.5 .5 .5) (-8 -8 -8) (8 8 8) trigger1 trigger2 trigger3 trigger4 trigger5 trigger6 trigger7 trigger8 +Once this trigger is touched/used, any trigger_crosslevel_target with the same trigger number is automatically used when a level is started within the same unit. It is OK to check multiple triggers. Message, delay, target, and killtarget also work. +*/ +void trigger_crosslevel_trigger_use (edict_t *self, edict_t *other, edict_t *activator) +{ + game.serverflags |= self->spawnflags; + G_FreeEdict (self); +} + +void SP_target_crosslevel_trigger (edict_t *self) +{ + self->svflags = SVF_NOCLIENT; + self->use = trigger_crosslevel_trigger_use; +} + +/*QUAKED target_crosslevel_target (.5 .5 .5) (-8 -8 -8) (8 8 8) trigger1 trigger2 trigger3 trigger4 trigger5 trigger6 trigger7 trigger8 +Triggered by a trigger_crosslevel elsewhere within a unit. If multiple triggers are checked, all must be true. Delay, target and +killtarget also work. + +"delay" delay before using targets if the trigger has been activated (default 1) +*/ +void target_crosslevel_target_think (edict_t *self) +{ + if (self->spawnflags == (game.serverflags & SFL_CROSS_TRIGGER_MASK & self->spawnflags)) + { + G_UseTargets (self, self); + G_FreeEdict (self); + } +} + +void SP_target_crosslevel_target (edict_t *self) +{ + if (! self->delay) + self->delay = 1; + self->svflags = SVF_NOCLIENT; + + self->think = target_crosslevel_target_think; + self->nextthink = level.time + self->delay; +} + +//========================================================== + +/*QUAKED target_laser (0 .5 .8) (-8 -8 -8) (8 8 8) START_ON RED GREEN BLUE YELLOW ORANGE FAT +When triggered, fires a laser. You can either set a target +or a direction. +*/ + +void target_laser_think (edict_t *self) +{ + edict_t *ignore; + vec3_t start; + vec3_t end; + trace_t tr; + vec3_t point; + vec3_t last_movedir; + int count; + + if (self->spawnflags & 0x80000000) + count = 8; + else + count = 4; + + if (self->enemy) + { + VectorCopy (self->movedir, last_movedir); + VectorMA (self->enemy->absmin, 0.5, self->enemy->size, point); + VectorSubtract (point, self->s.origin, self->movedir); + VectorNormalize (self->movedir); + if (!VectorCompare(self->movedir, last_movedir)) + self->spawnflags |= 0x80000000; + } + + ignore = self; + VectorCopy (self->s.origin, start); + VectorMA (start, 2048, self->movedir, end); + while(1) + { + tr = gi.trace (start, NULL, NULL, end, ignore, CONTENTS_SOLID|CONTENTS_MONSTER|CONTENTS_DEADMONSTER); + + VectorCopy (tr.endpos,self->moveinfo.end_origin); + + if (!tr.ent) + break; + + // hurt it if we can + if ((tr.ent->takedamage) && !(tr.ent->flags & FL_IMMUNE_LASER)) + T_Damage (tr.ent, self, self->activator, self->movedir, tr.endpos, vec3_origin, self->dmg, 1, DAMAGE_ENERGY, MOD_TARGET_LASER); + + // if we hit something that's not a monster or player or is immune to lasers, we're done + if (!(tr.ent->svflags & SVF_MONSTER) && (!tr.ent->client)) + { + if (self->spawnflags & 0x80000000) + { + self->spawnflags &= ~0x80000000; + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_LASER_SPARKS); + gi.WriteByte (count); + gi.WritePosition (tr.endpos); + gi.WriteDir (tr.plane.normal); + gi.WriteByte (self->s.skinnum); + gi.multicast (tr.endpos, MULTICAST_PVS); + } + break; + } + + ignore = tr.ent; + VectorCopy (tr.endpos, start); + } + + VectorCopy (tr.endpos, self->s.old_origin); + + self->nextthink = level.time + FRAMETIME; +} + +void target_laser_on (edict_t *self) +{ + if (!self->activator) + self->activator = self; + self->spawnflags |= 0x80000001; + self->svflags &= ~SVF_NOCLIENT; + target_laser_think (self); +} + +void target_laser_off (edict_t *self) +{ + self->spawnflags &= ~1; + self->svflags |= SVF_NOCLIENT; + self->nextthink = 0; +} + +void target_laser_use (edict_t *self, edict_t *other, edict_t *activator) +{ + self->activator = activator; + if (self->spawnflags & 1) + target_laser_off (self); + else + target_laser_on (self); +} + +void target_laser_start (edict_t *self) +{ + edict_t *ent; + + self->movetype = MOVETYPE_NONE; + self->solid = SOLID_NOT; + self->s.renderfx |= RF_BEAM|RF_TRANSLUCENT; + self->s.modelindex = 1; // must be non-zero + + // set the beam diameter + if (self->spawnflags & 64) + self->s.frame = 16; + else + self->s.frame = 4; + + // set the color + if (self->spawnflags & 2) + self->s.skinnum = 0xf2f2f0f0; + else if (self->spawnflags & 4) + self->s.skinnum = 0xd0d1d2d3; + else if (self->spawnflags & 8) + self->s.skinnum = 0xf3f3f1f1; + else if (self->spawnflags & 16) + self->s.skinnum = 0xdcdddedf; + else if (self->spawnflags & 32) + self->s.skinnum = 0xe0e1e2e3; + + if (!self->enemy) + { + if (self->target) + { + ent = G_Find (NULL, FOFS(targetname), self->target); + if (!ent) + gi.dprintf ("%s at %s: %s is a bad target\n", self->classname, vtos(self->s.origin), self->target); + self->enemy = ent; + } + else + { + G_SetMovedir (self->s.angles, self->movedir); + } + } + self->use = target_laser_use; + self->think = target_laser_think; + + if (!self->dmg) + self->dmg = 1; + + VectorSet (self->mins, -8, -8, -8); + VectorSet (self->maxs, 8, 8, 8); + gi.linkentity (self); + + if (self->spawnflags & 1) + target_laser_on (self); + else + target_laser_off (self); +} + +void SP_target_laser (edict_t *self) +{ + // let everything else get spawned before we start firing + self->think = target_laser_start; + self->nextthink = level.time + 1; +} + +//========================================================== +// RAFAEL 15-APR-98 +/*QUAKED target_mal_laser (1 0 0) (-4 -4 -4) (4 4 4) START_ON RED GREEN BLUE YELLOW ORANGE FAT +Mal's laser +*/ +void target_mal_laser_on (edict_t *self) +{ + if (!self->activator) + self->activator = self; + self->spawnflags |= 0x80000001; + self->svflags &= ~SVF_NOCLIENT; + // target_laser_think (self); + self->nextthink = level.time + self->wait + self->delay; +} + +void target_mal_laser_off (edict_t *self) +{ + self->spawnflags &= ~1; + self->svflags |= SVF_NOCLIENT; + self->nextthink = 0; +} + +void target_mal_laser_use (edict_t *self, edict_t *other, edict_t *activator) +{ + self->activator = activator; + if (self->spawnflags & 1) + target_mal_laser_off (self); + else + target_mal_laser_on (self); +} + +void mal_laser_think (edict_t *self) +{ + target_laser_think (self); + self->nextthink = level.time + self->wait + 0.1; + self->spawnflags |= 0x80000000; +} + +void SP_target_mal_laser (edict_t *self) +{ + self->movetype = MOVETYPE_NONE; + self->solid = SOLID_NOT; + self->s.renderfx |= RF_BEAM|RF_TRANSLUCENT; + self->s.modelindex = 1; // must be non-zero + + // set the beam diameter + if (self->spawnflags & 64) + self->s.frame = 16; + else + self->s.frame = 4; + + // set the color + if (self->spawnflags & 2) + self->s.skinnum = 0xf2f2f0f0; + else if (self->spawnflags & 4) + self->s.skinnum = 0xd0d1d2d3; + else if (self->spawnflags & 8) + self->s.skinnum = 0xf3f3f1f1; + else if (self->spawnflags & 16) + self->s.skinnum = 0xdcdddedf; + else if (self->spawnflags & 32) + self->s.skinnum = 0xe0e1e2e3; + + G_SetMovedir (self->s.angles, self->movedir); + + if (!self->delay) + self->delay = 0.1; + + if (!self->wait) + self->wait = 0.1; + + if (!self->dmg) + self->dmg = 5; + + VectorSet (self->mins, -8, -8, -8); + VectorSet (self->maxs, 8, 8, 8); + + self->nextthink = level.time + self->delay; + self->think = mal_laser_think; + + self->use = target_mal_laser_use; + + gi.linkentity (self); + + if (self->spawnflags & 1) + target_mal_laser_on (self); + else + target_mal_laser_off (self); +} +// END 15-APR-98 +//========================================================== +/*QUAKED target_lightramp (0 .5 .8) (-8 -8 -8) (8 8 8) TOGGLE +speed How many seconds the ramping will take +message two letters; starting lightlevel and ending lightlevel +*/ + +void target_lightramp_think (edict_t *self) +{ + char style[2]; + + style[0] = 'a' + self->movedir[0] + (level.time - self->timestamp) / FRAMETIME * self->movedir[2]; + style[1] = 0; + gi.configstring (CS_LIGHTS+self->enemy->style, style); + + if ((level.time - self->timestamp) < self->speed) + { + self->nextthink = level.time + FRAMETIME; + } + else if (self->spawnflags & 1) + { + char temp; + + temp = self->movedir[0]; + self->movedir[0] = self->movedir[1]; + self->movedir[1] = temp; + self->movedir[2] *= -1; + } +} + +void target_lightramp_use (edict_t *self, edict_t *other, edict_t *activator) +{ + if (!self->enemy) + { + edict_t *e; + + // check all the targets + e = NULL; + while (1) + { + e = G_Find (e, FOFS(targetname), self->target); + if (!e) + break; + if (strcmp(e->classname, "light") != 0) + { + gi.dprintf("%s at %s ", self->classname, vtos(self->s.origin)); + gi.dprintf("target %s (%s at %s) is not a light\n", self->target, e->classname, vtos(e->s.origin)); + } + else + { + self->enemy = e; + } + } + + if (!self->enemy) + { + gi.dprintf("%s target %s not found at %s\n", self->classname, self->target, vtos(self->s.origin)); + G_FreeEdict (self); + return; + } + } + + self->timestamp = level.time; + target_lightramp_think (self); +} + +void SP_target_lightramp (edict_t *self) +{ + if (!self->message || strlen(self->message) != 2 || self->message[0] < 'a' || self->message[0] > 'z' || self->message[1] < 'a' || self->message[1] > 'z' || self->message[0] == self->message[1]) + { + gi.dprintf("target_lightramp has bad ramp (%s) at %s\n", self->message, vtos(self->s.origin)); + G_FreeEdict (self); + return; + } + + if (deathmatch->value) + { + G_FreeEdict (self); + return; + } + + if (!self->target) + { + gi.dprintf("%s with no target at %s\n", self->classname, vtos(self->s.origin)); + G_FreeEdict (self); + return; + } + + self->svflags |= SVF_NOCLIENT; + self->use = target_lightramp_use; + self->think = target_lightramp_think; + + self->movedir[0] = self->message[0] - 'a'; + self->movedir[1] = self->message[1] - 'a'; + self->movedir[2] = (self->movedir[1] - self->movedir[0]) / (self->speed / FRAMETIME); +} + +//========================================================== + +/*QUAKED target_earthquake (1 0 0) (-8 -8 -8) (8 8 8) +When triggered, this initiates a level-wide earthquake. +All players and monsters are affected. +"speed" severity of the quake (default:200) +"count" duration of the quake (default:5) +*/ + +void target_earthquake_think (edict_t *self) +{ + int i; + edict_t *e; + + if (self->last_move_time < level.time) + { + gi.positioned_sound (self->s.origin, self, CHAN_AUTO, self->noise_index, 1.0, ATTN_NONE, 0); + self->last_move_time = level.time + 0.5; + } + + for (i=1, e=g_edicts+i; i < globals.num_edicts; i++,e++) + { + if (!e->inuse) + continue; + if (!e->client) + continue; + if (!e->groundentity) + continue; + + e->groundentity = NULL; + e->velocity[0] += crandom()* 150; + e->velocity[1] += crandom()* 150; + e->velocity[2] = self->speed * (100.0 / e->mass); + } + + if (level.time < self->timestamp) + self->nextthink = level.time + FRAMETIME; +} + +void target_earthquake_use (edict_t *self, edict_t *other, edict_t *activator) +{ + self->timestamp = level.time + self->count; + self->nextthink = level.time + FRAMETIME; + self->activator = activator; + self->last_move_time = 0; +} + +void SP_target_earthquake (edict_t *self) +{ + if (!self->targetname) + gi.dprintf("untargeted %s at %s\n", self->classname, vtos(self->s.origin)); + + if (!self->count) + self->count = 5; + + if (!self->speed) + self->speed = 200; + + self->svflags |= SVF_NOCLIENT; + self->think = target_earthquake_think; + self->use = target_earthquake_use; + + self->noise_index = gi.soundindex ("world/quake.wav"); +} diff --git a/src/g_trigger.c b/src/g_trigger.c new file mode 100644 index 0000000..dd10968 --- /dev/null +++ b/src/g_trigger.c @@ -0,0 +1,692 @@ +#include "g_local.h" + + +void InitTrigger (edict_t *self) +{ + if (!VectorCompare (self->s.angles, vec3_origin)) + G_SetMovedir (self->s.angles, self->movedir); + + self->solid = SOLID_TRIGGER; + self->movetype = MOVETYPE_NONE; + gi.setmodel (self, self->model); + self->svflags = SVF_NOCLIENT; +} + + +// the wait time has passed, so set back up for another activation +void multi_wait (edict_t *ent) +{ + ent->nextthink = 0; +} + + +// the trigger was just activated +// ent->activator should be set to the activator so it can be held through a delay +// so wait for the delay time before firing +void multi_trigger (edict_t *ent) +{ + if (ent->nextthink) + return; // already been triggered + + G_UseTargets (ent, ent->activator); + + if (ent->wait > 0) + { + ent->think = multi_wait; + ent->nextthink = level.time + ent->wait; + } + else + { // we can't just remove (self) here, because this is a touch function + // called while looping through area links... + ent->touch = NULL; + ent->nextthink = level.time + FRAMETIME; + ent->think = G_FreeEdict; + } +} + +void Use_Multi (edict_t *ent, edict_t *other, edict_t *activator) +{ + ent->activator = activator; + multi_trigger (ent); +} + +void Touch_Multi (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + if(other->client) + { + if (self->spawnflags & 2) + return; + } + else if (other->svflags & SVF_MONSTER) + { + if (!(self->spawnflags & 1)) + return; + } + else + return; + + if (!VectorCompare(self->movedir, vec3_origin)) + { + vec3_t forward; + + AngleVectors(other->s.angles, forward, NULL, NULL); + if (_DotProduct(forward, self->movedir) < 0) + return; + } + + self->activator = other; + multi_trigger (self); +} + +/*QUAKED trigger_multiple (.5 .5 .5) ? MONSTER NOT_PLAYER TRIGGERED +Variable sized repeatable trigger. Must be targeted at one or more entities. +If "delay" is set, the trigger waits some time after activating before firing. +"wait" : Seconds between triggerings. (.2 default) +sounds +1) secret +2) beep beep +3) large switch +4) +set "message" to text string +*/ +void trigger_enable (edict_t *self, edict_t *other, edict_t *activator) +{ + self->solid = SOLID_TRIGGER; + self->use = Use_Multi; + gi.linkentity (self); +} + +void SP_trigger_multiple (edict_t *ent) +{ + if (ent->sounds == 1) + ent->noise_index = gi.soundindex ("misc/secret.wav"); + else if (ent->sounds == 2) + ent->noise_index = gi.soundindex ("misc/talk.wav"); + else if (ent->sounds == 3) + ent->noise_index = gi.soundindex ("misc/trigger1.wav"); + + if (!ent->wait) + ent->wait = 0.2; + ent->touch = Touch_Multi; + ent->movetype = MOVETYPE_NONE; + ent->svflags |= SVF_NOCLIENT; + + + if (ent->spawnflags & 4) + { + ent->solid = SOLID_NOT; + ent->use = trigger_enable; + } + else + { + ent->solid = SOLID_TRIGGER; + ent->use = Use_Multi; + } + + if (!VectorCompare(ent->s.angles, vec3_origin)) + G_SetMovedir (ent->s.angles, ent->movedir); + + gi.setmodel (ent, ent->model); + gi.linkentity (ent); +} + + +/*QUAKED trigger_once (.5 .5 .5) ? x x TRIGGERED +Triggers once, then removes itself. +You must set the key "target" to the name of another object in the level that has a matching "targetname". + +If TRIGGERED, this trigger must be triggered before it is live. + +sounds + 1) secret + 2) beep beep + 3) large switch + 4) + +"message" string to be displayed when triggered +*/ + +void SP_trigger_once(edict_t *ent) +{ + // make old maps work because I messed up on flag assignments here + // triggered was on bit 1 when it should have been on bit 4 + if (ent->spawnflags & 1) + { + vec3_t v; + + VectorMA (ent->mins, 0.5, ent->size, v); + ent->spawnflags &= ~1; + ent->spawnflags |= 4; + gi.dprintf("fixed TRIGGERED flag on %s at %s\n", ent->classname, vtos(v)); + } + + ent->wait = -1; + SP_trigger_multiple (ent); +} + +/*QUAKED trigger_relay (.5 .5 .5) (-8 -8 -8) (8 8 8) +This fixed size trigger cannot be touched, it can only be fired by other events. +*/ +void trigger_relay_use (edict_t *self, edict_t *other, edict_t *activator) +{ + G_UseTargets (self, activator); +} + +void SP_trigger_relay (edict_t *self) +{ + self->use = trigger_relay_use; +} + + +/* +============================================================================== + +trigger_key + +============================================================================== +*/ + +/*QUAKED trigger_key (.5 .5 .5) (-8 -8 -8) (8 8 8) +A relay trigger that only fires it's targets if player has the proper key. +Use "item" to specify the required key, for example "key_data_cd" +*/ +void trigger_key_use (edict_t *self, edict_t *other, edict_t *activator) +{ + int index; + + if (!self->item) + return; + if (!activator->client) + return; + + index = ITEM_INDEX(self->item); + if (!activator->client->pers.inventory[index]) + { + if (level.time < self->touch_debounce_time) + return; + self->touch_debounce_time = level.time + 5.0; + if(!(activator->svflags & SVF_MONSTER)) + gi.centerprintf (activator, "You need the %s", self->item->pickup_name); + gi.sound (activator, CHAN_AUTO, gi.soundindex ("misc/keytry.wav"), 1, ATTN_NORM, 0); + return; + } + + gi.sound (activator, CHAN_AUTO, gi.soundindex ("misc/keyuse.wav"), 1, ATTN_NORM, 0); + if (coop->value) + { + int player; + edict_t *ent; + + if (strcmp(self->item->classname, "key_power_cube") == 0) + { + int cube; + + for (cube = 0; cube < 8; cube++) + if (activator->client->pers.power_cubes & (1 << cube)) + break; + for (player = 1; player <= game.maxclients; player++) + { + ent = &g_edicts[player]; + if (!ent->inuse) + continue; + if (!ent->client) + continue; + if (ent->client->pers.power_cubes & (1 << cube)) + { + ent->client->pers.inventory[index]--; + ent->client->pers.power_cubes &= ~(1 << cube); + } + } + } + else + { + for (player = 1; player <= game.maxclients; player++) + { + ent = &g_edicts[player]; + if (!ent->inuse) + continue; + if (!ent->client) + continue; + ent->client->pers.inventory[index] = 0; + } + } + } + else + { + activator->client->pers.inventory[index]--; + } + + G_UseTargets (self, activator); + + self->use = NULL; +} + +void SP_trigger_key (edict_t *self) +{ + if (!st.item) + { + gi.dprintf("no key item for trigger_key at %s\n", vtos(self->s.origin)); + return; + } + self->item = FindItemByClassname (st.item); + + if (!self->item) + { + gi.dprintf("item %s not found for trigger_key at %s\n", st.item, vtos(self->s.origin)); + return; + } + + if (!self->target) + { + gi.dprintf("%s at %s has no target\n", self->classname, vtos(self->s.origin)); + return; + } + + gi.soundindex ("misc/keytry.wav"); + gi.soundindex ("misc/keyuse.wav"); + + self->use = trigger_key_use; +} + + +/* +============================================================================== + +trigger_counter + +============================================================================== +*/ + +/*QUAKED trigger_counter (.5 .5 .5) ? nomessage +Acts as an intermediary for an action that takes multiple inputs. + +If nomessage is not set, t will print "1 more.. " etc when triggered and "sequence complete" when finished. + +After the counter has been triggered "count" times (default 2), it will fire all of it's targets and remove itself. +*/ + +void trigger_counter_use(edict_t *self, edict_t *other, edict_t *activator) +{ + if (self->count == 0) + return; + + self->count--; + + if (self->count) + { + if (! (self->spawnflags & 1) && !(self->svflags & SVF_MONSTER)) + { + gi.centerprintf(activator, "%i more to go...", self->count); + gi.sound (activator, CHAN_AUTO, gi.soundindex ("misc/talk1.wav"), 1, ATTN_NORM, 0); + } + return; + } + + if (! (self->spawnflags & 1) && !(self->svflags & SVF_MONSTER)) + { + gi.centerprintf(activator, "Sequence completed!"); + gi.sound (activator, CHAN_AUTO, gi.soundindex ("misc/talk1.wav"), 1, ATTN_NORM, 0); + } + self->activator = activator; + multi_trigger (self); +} + +void SP_trigger_counter (edict_t *self) +{ + self->wait = -1; + if (!self->count) + self->count = 2; + + self->use = trigger_counter_use; +} + + +/* +============================================================================== + +trigger_always + +============================================================================== +*/ + +/*QUAKED trigger_always (.5 .5 .5) (-8 -8 -8) (8 8 8) +This trigger will always fire. It is activated by the world. +*/ +void SP_trigger_always (edict_t *ent) +{ + // we must have some delay to make sure our use targets are present + if (ent->delay < 0.2) + ent->delay = 0.2; + G_UseTargets(ent, ent); +} + + +/* +============================================================================== + +trigger_push + +============================================================================== +*/ + +#if 0 +#define PUSH_ONCE 1 + +static int windsound; + +void trigger_push_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + if (strcmp(other->classname, "grenade") == 0) + { + VectorScale (self->movedir, self->speed * 10, other->velocity); + } + else if (other->health > 0) + { + VectorScale (self->movedir, self->speed * 10, other->velocity); + + if (other->client) + { + // don't take falling damage immediately from this + VectorCopy (other->velocity, other->client->oldvelocity); + if (other->fly_sound_debounce_time < level.time) + { + other->fly_sound_debounce_time = level.time + 1.5; + gi.sound (other, CHAN_AUTO, windsound, 1, ATTN_NORM, 0); + } + } + } + if (self->spawnflags & PUSH_ONCE) + G_FreeEdict (self); +} + +void SP_trigger_push (edict_t *self) +{ + InitTrigger (self); + windsound = gi.soundindex ("misc/windfly.wav"); + self->touch = trigger_push_touch; + if (!self->speed) + self->speed = 1000; + gi.linkentity (self); +} +#endif + +// RAFAEL +#define PUSH_ONCE 1 + +static int windsound; + +void trigger_push_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + if (strcmp(other->classname, "grenade") == 0) + { + VectorScale (self->movedir, self->speed * 10, other->velocity); + } + else if (other->health > 0) + { + VectorScale (self->movedir, self->speed * 10, other->velocity); + + if (other->client) + { + // don't take falling damage immediately from this + VectorCopy (other->velocity, other->client->oldvelocity); + if (other->fly_sound_debounce_time < level.time) + { + other->fly_sound_debounce_time = level.time + 1.5; + gi.sound (other, CHAN_AUTO, windsound, 1, ATTN_NORM, 0); + } + } + } + if (self->spawnflags & PUSH_ONCE) + G_FreeEdict (self); +} + + +/*QUAKED trigger_push (.5 .5 .5) ? PUSH_ONCE +Pushes the player +"speed" defaults to 1000 +*/ +void trigger_push_active (edict_t *self); + +void trigger_effect (edict_t *self) +{ + vec3_t origin; + vec3_t size; + int i; + + VectorScale (self->size, 0.5, size); + VectorAdd (self->absmin, size, origin); + + for (i=0; i<10; i++) + { + origin[2] += (self->speed * 0.01) * (i + random()); + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_TUNNEL_SPARKS); + gi.WriteByte (1); + gi.WritePosition (origin); + gi.WriteDir (vec3_origin); + gi.WriteByte (0x74 + (rand()&7)); + gi.multicast (self->s.origin, MULTICAST_PVS); + } + +} + +void trigger_push_inactive (edict_t *self) +{ + if (self->delay > level.time) + { + self->nextthink = level.time + 0.1; + } + else + { + self->touch = trigger_push_touch; + self->think = trigger_push_active; + self->nextthink = level.time + 0.1; + self->delay = self->nextthink + self->wait; + } +} + +void trigger_push_active (edict_t *self) +{ + if (self->delay > level.time) + { + self->nextthink = level.time + 0.1; + trigger_effect (self); + } + else + { + self->touch = NULL; + self->think = trigger_push_inactive; + self->nextthink = level.time + 0.1; + self->delay = self->nextthink + self->wait; + } +} + +void SP_trigger_push (edict_t *self) +{ + InitTrigger (self); + windsound = gi.soundindex ("misc/windfly.wav"); + self->touch = trigger_push_touch; + + if (self->spawnflags & 2) + { + if (!self->wait) + self->wait = 10; + + self->think = trigger_push_active; + self->nextthink = level.time + 0.1; + self->delay = self->nextthink + self->wait; + } + + if (!self->speed) + self->speed = 1000; + + gi.linkentity (self); + +} + +/* +============================================================================== + +trigger_hurt + +============================================================================== +*/ + +/*QUAKED trigger_hurt (.5 .5 .5) ? START_OFF TOGGLE SILENT NO_PROTECTION SLOW +Any entity that touches this will be hurt. + +It does dmg points of damage each server frame + +SILENT supresses playing the sound +SLOW changes the damage rate to once per second +NO_PROTECTION *nothing* stops the damage + +"dmg" default 5 (whole numbers only) + +*/ +void hurt_use (edict_t *self, edict_t *other, edict_t *activator) +{ + if (self->solid == SOLID_NOT) + self->solid = SOLID_TRIGGER; + else + self->solid = SOLID_NOT; + gi.linkentity (self); + + if (!(self->spawnflags & 2)) + self->use = NULL; +} + + +void hurt_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + int dflags; + + if (!other->takedamage) + return; + + if (self->timestamp > level.time) + return; + + if (self->spawnflags & 16) + self->timestamp = level.time + 1; + else + self->timestamp = level.time + FRAMETIME; + + if (!(self->spawnflags & 4)) + { + if ((level.framenum % 10) == 0) + gi.sound (other, CHAN_AUTO, self->noise_index, 1, ATTN_NORM, 0); + } + + if (self->spawnflags & 8) + dflags = DAMAGE_NO_PROTECTION; + else + dflags = 0; + T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, self->dmg, self->dmg, dflags, MOD_TRIGGER_HURT); +} + +void SP_trigger_hurt (edict_t *self) +{ + InitTrigger (self); + + self->noise_index = gi.soundindex ("world/electro.wav"); + self->touch = hurt_touch; + + if (!self->dmg) + self->dmg = 5; + + if (self->spawnflags & 1) + self->solid = SOLID_NOT; + else + self->solid = SOLID_TRIGGER; + + if (self->spawnflags & 2) + self->use = hurt_use; + + gi.linkentity (self); +} + + +/* +============================================================================== + +trigger_gravity + +============================================================================== +*/ + +/*QUAKED trigger_gravity (.5 .5 .5) ? +Changes the touching entites gravity to +the value of "gravity". 1.0 is standard +gravity for the level. +*/ + +void trigger_gravity_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + other->gravity = self->gravity; +} + +void SP_trigger_gravity (edict_t *self) +{ + if (st.gravity == 0 ) + { + gi.dprintf("trigger_gravity without gravity set at %s\n", vtos(self->s.origin)); + G_FreeEdict (self); + return; + } + + InitTrigger (self); + self->gravity = atoi(st.gravity); + self->touch = trigger_gravity_touch; +} + + +/* +============================================================================== + +trigger_monsterjump + +============================================================================== +*/ + +/*QUAKED trigger_monsterjump (.5 .5 .5) ? +Walking monsters that touch this will jump in the direction of the trigger's angle +"speed" default to 200, the speed thrown forward +"height" default to 200, the speed thrown upwards +*/ + +void trigger_monsterjump_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + if (other->client) return; + + if (other->flags & (FL_FLY | FL_SWIM) ) + return; + if (other->svflags & SVF_DEADMONSTER) + return; + if ( !(other->svflags & SVF_MONSTER)) + return; + +// set XY even if not on ground, so the jump will clear lips + other->velocity[0] = self->movedir[0] * self->speed; + other->velocity[1] = self->movedir[1] * self->speed; + + if (!other->groundentity) + return; + + other->groundentity = NULL; + other->velocity[2] = self->movedir[2]; +} + +void SP_trigger_monsterjump (edict_t *self) +{ + if (!self->speed) + self->speed = 200; + if (!st.height) + st.height = 200; + if (self->s.angles[YAW] == 0) + self->s.angles[YAW] = 360; + InitTrigger (self); + self->touch = trigger_monsterjump_touch; + self->movedir[2] = st.height; +} + diff --git a/src/g_utils.c b/src/g_utils.c new file mode 100644 index 0000000..c719b7d --- /dev/null +++ b/src/g_utils.c @@ -0,0 +1,591 @@ +// g_utils.c -- misc utility functions for game module + +#include "g_local.h" + + +void G_ProjectSource (vec3_t point, vec3_t distance, vec3_t forward, vec3_t right, vec3_t result) +{ + result[0] = point[0] + forward[0] * distance[0] + right[0] * distance[1]; + result[1] = point[1] + forward[1] * distance[0] + right[1] * distance[1]; + result[2] = point[2] + forward[2] * distance[0] + right[2] * distance[1] + distance[2]; +} + + +/* +============= +G_Find + +Searches all active entities for the next one that holds +the matching string at fieldofs (use the FOFS() macro) in the structure. + +Searches beginning at the edict after from, or the beginning if NULL +NULL will be returned if the end of the list is reached. + +============= +*/ +edict_t *G_Find (edict_t *from, int fieldofs, char *match) +{ + char *s; + + if (!from) + from = g_edicts; + else + from++; + + for ( ; from < &g_edicts[globals.num_edicts] ; from++) + { + if (!from->inuse) + continue; + s = *(char **) ((byte *)from + fieldofs); + if (!s) + continue; + if (!Q_stricmp (s, match)) + return from; + } + + return NULL; +} + + +/* +================= +findradius + +Returns entities that have origins within a spherical area + +findradius (origin, radius) +================= +*/ +edict_t *findradius (edict_t *from, vec3_t org, float rad) +{ + vec3_t eorg; + int j; + + if (!from) + from = g_edicts; + else + from++; + for ( ; from < &g_edicts[globals.num_edicts]; from++) + { + if (!from->inuse) + continue; + if (from->solid == SOLID_NOT) + continue; + for (j=0 ; j<3 ; j++) + eorg[j] = org[j] - (from->s.origin[j] + (from->mins[j] + from->maxs[j])*0.5); + if (VectorLength(eorg) > rad) + continue; + return from; + } + + return NULL; +} + + +/* +============= +G_PickTarget + +Searches all active entities for the next one that holds +the matching string at fieldofs (use the FOFS() macro) in the structure. + +Searches beginning at the edict after from, or the beginning if NULL +NULL will be returned if the end of the list is reached. + +============= +*/ +#define MAXCHOICES 8 + +edict_t *G_PickTarget (char *targetname) +{ + edict_t *ent = NULL; + int num_choices = 0; + edict_t *choice[MAXCHOICES]; + + if (!targetname) + { + gi.dprintf("G_PickTarget called with NULL targetname\n"); + return NULL; + } + + while(1) + { + ent = G_Find (ent, FOFS(targetname), targetname); + if (!ent) + break; + choice[num_choices++] = ent; + if (num_choices == MAXCHOICES) + break; + } + + if (!num_choices) + { + gi.dprintf("G_PickTarget: target %s not found\n", targetname); + return NULL; + } + + return choice[rand() % num_choices]; +} + +void Think_Delay (edict_t *ent) +{ + G_UseTargets (ent, ent->activator); + G_FreeEdict (ent); +} + +/* +============================== +G_UseTargets + +the global "activator" should be set to the entity that initiated the firing. + +If self.delay is set, a DelayedUse entity will be created that will actually +do the SUB_UseTargets after that many seconds have passed. + +Centerprints any self.message to the activator. + +Search for (string)targetname in all entities that +match (string)self.target and call their .use function + +============================== +*/ +void G_UseTargets (edict_t *ent, edict_t *activator) +{ + edict_t *t; + +// +// check for a delay +// + if (ent->delay) + { + // create a temp object to fire at a later time + t = G_Spawn(); + t->classname = "DelayedUse"; + t->nextthink = level.time + ent->delay; + t->think = Think_Delay; + t->activator = activator; + if (!activator) + gi.dprintf ("Think_Delay with no activator\n"); + t->message = ent->message; + t->target = ent->target; + t->killtarget = ent->killtarget; + return; + } + + +// +// print the message +// + if ((ent->message) && !(activator->svflags & SVF_MONSTER)) + { + gi.centerprintf (activator, "%s", ent->message); + if (ent->noise_index) + gi.sound (activator, CHAN_AUTO, ent->noise_index, 1, ATTN_NORM, 0); + else + gi.sound (activator, CHAN_AUTO, gi.soundindex ("misc/talk1.wav"), 1, ATTN_NORM, 0); + } + +// +// kill killtargets +// + if (ent->killtarget) + { + t = NULL; + while ((t = G_Find (t, FOFS(targetname), ent->killtarget))) + { + G_FreeEdict (t); + if (!ent->inuse) + { + gi.dprintf("entity was removed while using killtargets\n"); + return; + } + } + } + +// +// fire targets +// + if (ent->target) + { + t = NULL; + while ((t = G_Find (t, FOFS(targetname), ent->target))) + { + // doors fire area portals in a specific way + if (!Q_stricmp(t->classname, "func_areaportal") && + (!Q_stricmp(ent->classname, "func_door") || !Q_stricmp(ent->classname, "func_door_rotating"))) + continue; + + if (t == ent) + { + gi.dprintf ("WARNING: Entity used itself.\n"); + } + else + { + if (t->use) + t->use (t, ent, activator); + } + if (!ent->inuse) + { + gi.dprintf("entity was removed while using targets\n"); + return; + } + } + } +} + + +/* +============= +TempVector + +This is just a convenience function +for making temporary vectors for function calls +============= +*/ +float *tv (float x, float y, float z) +{ + static int index; + static vec3_t vecs[8]; + float *v; + + // use an array so that multiple tempvectors won't collide + // for a while + v = vecs[index]; + index = (index + 1)&7; + + v[0] = x; + v[1] = y; + v[2] = z; + + return v; +} + + +/* +============= +VectorToString + +This is just a convenience function +for printing vectors +============= +*/ +char *vtos (vec3_t v) +{ + static int index; + static char str[8][32]; + char *s; + + // use an array so that multiple vtos won't collide + s = str[index]; + index = (index + 1)&7; + + Com_sprintf (s, 32, "(%i %i %i)", (int)v[0], (int)v[1], (int)v[2]); + + return s; +} + + +vec3_t VEC_UP = {0, -1, 0}; +vec3_t MOVEDIR_UP = {0, 0, 1}; +vec3_t VEC_DOWN = {0, -2, 0}; +vec3_t MOVEDIR_DOWN = {0, 0, -1}; + +void G_SetMovedir (vec3_t angles, vec3_t movedir) +{ + if (VectorCompare (angles, VEC_UP)) + { + VectorCopy (MOVEDIR_UP, movedir); + } + else if (VectorCompare (angles, VEC_DOWN)) + { + VectorCopy (MOVEDIR_DOWN, movedir); + } + else + { + AngleVectors (angles, movedir, NULL, NULL); + } + + VectorClear (angles); +} + + +float vectoyaw (vec3_t vec) +{ + float yaw; + + if (vec[YAW] == 0 && vec[PITCH] == 0) + yaw = 0; + else + { + yaw = (int) (atan2(vec[YAW], vec[PITCH]) * 180 / M_PI); + if (yaw < 0) + yaw += 360; + } + + return yaw; +} + + +void vectoangles (vec3_t value1, vec3_t angles) +{ + float forward; + float yaw, pitch; + + if (value1[1] == 0 && value1[0] == 0) + { + yaw = 0; + if (value1[2] > 0) + pitch = 90; + else + pitch = 270; + } + else + { + yaw = (int) (atan2(value1[1], value1[0]) * 180 / M_PI); + if (yaw < 0) + yaw += 360; + + forward = sqrt (value1[0]*value1[0] + value1[1]*value1[1]); + pitch = (int) (atan2(value1[2], forward) * 180 / M_PI); + if (pitch < 0) + pitch += 360; + } + + angles[PITCH] = -pitch; + angles[YAW] = yaw; + angles[ROLL] = 0; +} + +char *G_CopyString (char *in) +{ + char *out; + + out = gi.TagMalloc (strlen(in)+1, TAG_LEVEL); + strcpy (out, in); + return out; +} + + +void G_InitEdict (edict_t *e) +{ + e->inuse = true; + e->classname = "noclass"; + e->gravity = 1.0; + e->s.number = e - g_edicts; +} + +/* +================= +G_Spawn + +Either finds a free edict, or allocates a new one. +Try to avoid reusing an entity that was recently freed, because it +can cause the client to think the entity morphed into something else +instead of being removed and recreated, which can cause interpolated +angles and bad trails. +================= +*/ +edict_t *G_Spawn (void) +{ + int i; + edict_t *e; + + e = &g_edicts[(int)maxclients->value+1]; + for ( i=maxclients->value+1 ; iinuse && ( e->freetime < 2 || level.time - e->freetime > 0.5 ) ) + { + G_InitEdict (e); + return e; + } + } + + if (i == game.maxentities) + gi.error ("ED_Alloc: no free edicts"); + + globals.num_edicts++; + G_InitEdict (e); + return e; +} + +/* +================= +G_FreeEdict + +Marks the edict as free +================= +*/ +void G_FreeEdict (edict_t *ed) +{ + gi.unlinkentity (ed); // unlink from world + + if ((ed - g_edicts) <= (maxclients->value + BODY_QUEUE_SIZE)) + { +// gi.dprintf("tried to free special edict\n"); + return; + } + + memset (ed, 0, sizeof(*ed)); + ed->classname = "freed"; + ed->freetime = level.time; + ed->inuse = false; +} + + +/* +============ +G_TouchTriggers + +============ +*/ +void G_TouchTriggers (edict_t *ent) +{ + int i, num; + edict_t *touch[MAX_EDICTS], *hit; + + // dead things don't activate triggers! + if ((ent->client || (ent->svflags & SVF_MONSTER)) && (ent->health <= 0)) + return; + + num = gi.BoxEdicts (ent->absmin, ent->absmax, touch + , MAX_EDICTS, AREA_TRIGGERS); + + // be careful, it is possible to have an entity in this + // list removed before we get to it (killtriggered) + for (i=0 ; iinuse) + continue; + if (!hit->touch) + continue; + hit->touch (hit, ent, NULL, NULL); + if(ent->client) if(ent->client->zc.second_target == hit) + ent->client->zc.second_target = NULL; + + } +} + +/* +void G_TouchTriggers (edict_t *ent) +{ + int i, num; +//pon edict_t *touch[MAX_EDICTS], *hit; + + edict_t *e; + + if(ent->classname[0] != 'p') return; + + // dead things don't activate triggers! + if ((ent->client || (ent->svflags & SVF_MONSTER)) && (ent->health <= 0)) + return; +//pon + e = &g_edicts[(int)maxclients->value+1]; + for ( i=maxclients->value+1 ; iinuse) continue; + if(!e->touch) continue; + if(e->solid != SOLID_TRIGGER) continue; + + if(e->absmax[0] < ent->absmin[0]) continue; + if(ent->absmax[0] < e->absmin[0]) continue; + if(e->absmax[1] < ent->absmin[1]) continue; + if(ent->absmax[1] < e->absmin[1]) continue; + if(e->absmax[2] < ent->absmin[2]) continue; + if(ent->absmax[2] < e->absmin[2]) continue; + + e->touch (e, ent, NULL, NULL); + + if(ent->client) if(ent->client->zc.second_target == e) + ent->client->zc.second_target = NULL; + } + return; +//pon + +// num = gi.BoxEdicts (ent->absmin, ent->absmax, touch +// , MAX_EDICTS, AREA_TRIGGERS); + + // be careful, it is possible to have an entity in this + // list removed before we get to it (killtriggered) +/* for (i=0 ; iinuse) + continue; + if (!hit->touch) + continue; + hit->touch (hit, ent, NULL, NULL); + } +}*/ + +/* +============ +G_TouchSolids + +Call after linking a new trigger in during gameplay +to force all entities it covers to immediately touch it +============ +*/ +void G_TouchSolids (edict_t *ent) +{ + int i, num; + edict_t *touch[MAX_EDICTS], *hit; + + num = gi.BoxEdicts (ent->absmin, ent->absmax, touch + , MAX_EDICTS, AREA_SOLID); + + // be careful, it is possible to have an entity in this + // list removed before we get to it (killtriggered) + for (i=0 ; iinuse) + continue; + if (ent->touch) + ent->touch (hit, ent, NULL, NULL); + if (!ent->inuse) + break; + } +} + + + + +/* +============================================================================== + +Kill box + +============================================================================== +*/ + +/* +================= +KillBox + +Kills all entities that would touch the proposed new positioning +of ent. Ent should be unlinked before calling this! +================= +*/ +qboolean KillBox (edict_t *ent) +{ + trace_t tr; + + while (1) + { + tr = gi.trace (ent->s.origin, ent->mins, ent->maxs, ent->s.origin, NULL, MASK_PLAYERSOLID); + if (!tr.ent) + break; + + // nail it + T_Damage (tr.ent, ent, ent, vec3_origin, ent->s.origin, vec3_origin, 100000, 0, DAMAGE_NO_PROTECTION, MOD_TELEFRAG); + + // if we didn't kill it, fail + if (tr.ent->solid) + return false; + } + + return true; // all clear +} diff --git a/src/g_weapon.c b/src/g_weapon.c new file mode 100644 index 0000000..f1a4ce4 --- /dev/null +++ b/src/g_weapon.c @@ -0,0 +1,1670 @@ +#include "g_local.h" +#include "bot.h" + + +/* +============= +visible + +returns 1 if the entity is visible to self, even if not infront () +============= +*/ +qboolean visible (edict_t *self, edict_t *other) +{ + vec3_t spot1; + vec3_t spot2; + trace_t trace; + + VectorCopy (self->s.origin, spot1); + spot1[2] += self->viewheight; + VectorCopy (other->s.origin, spot2); + spot2[2] += other->viewheight; + trace = gi.trace (spot1, vec3_origin, vec3_origin, spot2, self, MASK_OPAQUE); + + if (trace.fraction == 1.0) + return true; + return false; +} + +/* +================= +check_dodge + +This is a support routine used when a client is firing +a non-instant attack weapon. It checks to see if a +monster's dodge function should be called. +================= +*/ + +static void check_dodge (edict_t *self, vec3_t start, vec3_t dir, int speed) +{ + vec3_t end; + vec3_t vx,vn; + trace_t tr; +// float eta; + + if(self->svflags & SVF_MONSTER) return; + + VectorSet(vn,-8,-8,-8); + VectorSet(vx, 8, 8, 8); + + VectorMA (start, 8192, dir, end); + tr = gi.trace (start, vn, vx, end, self, MASK_SHOT); + if ((tr.ent) && tr.ent->client && Q_stricmp (tr.ent->classname, "player") == 0 && (tr.ent->health > 0)) + { +// VectorCopy(tr.endpos,tr.ent->client->zc.aimedpos); +// VectorSubtract (tr.endpos, start, v); +// eta = (VectorLength(v) - tr.ent->maxs[0]) / speed; +// tr.ent->monsterinfo.dodge (tr.ent, self, eta); + if(!OnSameTeam(self,tr.ent)) self->client->zc.first_target = tr.ent; + } +} + +/* +================= +fire_hit + +Used for all impact (hit/punch/slash) attacks +================= +*/ +qboolean fire_hit (edict_t *self, vec3_t aim, int damage, int kick) +{ + trace_t tr; + vec3_t forward, right, up; + vec3_t v; + vec3_t point; + float range; + vec3_t dir; + + //see if enemy is in range + VectorSubtract (self->enemy->s.origin, self->s.origin, dir); + range = VectorLength(dir); + if (range > aim[0]) + return false; + + if (aim[1] > self->mins[0] && aim[1] < self->maxs[0]) + { + // the hit is straight on so back the range up to the edge of their bbox + range -= self->enemy->maxs[0]; + } + else + { + // this is a side hit so adjust the "right" value out to the edge of their bbox + if (aim[1] < 0) + aim[1] = self->enemy->mins[0]; + else + aim[1] = self->enemy->maxs[0]; + } + + VectorMA (self->s.origin, range, dir, point); + + tr = gi.trace (self->s.origin, NULL, NULL, point, self, MASK_SHOT); + if (tr.fraction < 1) + { + if (!tr.ent->takedamage) + return false; + // if it will hit any client/monster then hit the one we wanted to hit + if ((tr.ent->svflags & SVF_MONSTER) || (tr.ent->client)) + tr.ent = self->enemy; + } + + AngleVectors(self->s.angles, forward, right, up); + VectorMA (self->s.origin, range, forward, point); + VectorMA (point, aim[1], right, point); + VectorMA (point, aim[2], up, point); + VectorSubtract (point, self->enemy->s.origin, dir); + + // do the damage + T_Damage (tr.ent, self, self, dir, point, vec3_origin, damage, kick/2, DAMAGE_NO_KNOCKBACK, MOD_HIT); + + if (!(tr.ent->svflags & SVF_MONSTER) && (!tr.ent->client)) + return false; + + // do our special form of knockback here + VectorMA (self->enemy->absmin, 0.5, self->enemy->size, v); + VectorSubtract (v, point, v); + VectorNormalize (v); + VectorMA (self->enemy->velocity, kick, v, self->enemy->velocity); + if (self->enemy->velocity[2] > 0) + self->enemy->groundentity = NULL; + return true; +} + + +/* +================= +fire_lead + +This is an internal support routine used for bullet/pellet based weapons. +================= +*/ +static void fire_lead (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick, int te_impact, int hspread, int vspread, int mod) +{ + trace_t tr; + vec3_t dir; + vec3_t forward, right, up; + vec3_t end; + float r; + float u; + vec3_t water_start; + qboolean water = false; + int content_mask = MASK_SHOT | MASK_WATER; + + tr = gi.trace (self->s.origin, NULL, NULL, start, self, MASK_SHOT); + if (!(tr.fraction < 1.0)) + { + vectoangles (aimdir, dir); + AngleVectors (dir, forward, right, up); + + r = crandom()*hspread; + u = crandom()*vspread; + VectorMA (start, 8192, forward, end); + VectorMA (end, r, right, end); + VectorMA (end, u, up, end); + + if (gi.pointcontents (start) & MASK_WATER) + { + water = true; + VectorCopy (start, water_start); + content_mask &= ~MASK_WATER; + } + + tr = gi.trace (start, NULL, NULL, end, self, content_mask); + + // see if we hit water + if (tr.contents & MASK_WATER) + { + int color; + + water = true; + VectorCopy (tr.endpos, water_start); + + if (!VectorCompare (start, tr.endpos)) + { + if (tr.contents & CONTENTS_WATER) + { + if (strcmp(tr.surface->name, "*brwater") == 0) + color = SPLASH_BROWN_WATER; + else + color = SPLASH_BLUE_WATER; + } + else if (tr.contents & CONTENTS_SLIME) + color = SPLASH_SLIME; + else if (tr.contents & CONTENTS_LAVA) + color = SPLASH_LAVA; + else + color = SPLASH_UNKNOWN; + + if (color != SPLASH_UNKNOWN) + { + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_SPLASH); + gi.WriteByte (8); + gi.WritePosition (tr.endpos); + gi.WriteDir (tr.plane.normal); + gi.WriteByte (color); + gi.multicast (tr.endpos, MULTICAST_PVS); + } + + // change bullet's course when it enters water + VectorSubtract (end, start, dir); + vectoangles (dir, dir); + AngleVectors (dir, forward, right, up); + r = crandom()*hspread*2; + u = crandom()*vspread*2; + VectorMA (water_start, 8192, forward, end); + VectorMA (end, r, right, end); + VectorMA (end, u, up, end); + } + + // re-trace ignoring water this time + tr = gi.trace (water_start, NULL, NULL, end, self, MASK_SHOT); + } + } + + // send gun puff / flash + if (!((tr.surface) && (tr.surface->flags & SURF_SKY))) + { + if (tr.fraction < 1.0) + { + if (tr.ent->takedamage) + { + T_Damage (tr.ent, self, self, aimdir, tr.endpos, tr.plane.normal, damage, kick, DAMAGE_BULLET, mod); + } + else + { + if (strncmp (tr.surface->name, "sky", 3) != 0) + { + gi.WriteByte (svc_temp_entity); + gi.WriteByte (te_impact); + gi.WritePosition (tr.endpos); + gi.WriteDir (tr.plane.normal); + gi.multicast (tr.endpos, MULTICAST_PVS); + + if (self->client) + PlayerNoise(self, tr.endpos, PNOISE_IMPACT); + } + } + } + } + + // if went through water, determine where the end and make a bubble trail + if (water) + { + vec3_t pos; + + VectorSubtract (tr.endpos, water_start, dir); + VectorNormalize (dir); + VectorMA (tr.endpos, -2, dir, pos); + if (gi.pointcontents (pos) & MASK_WATER) + VectorCopy (pos, tr.endpos); + else + tr = gi.trace (pos, NULL, NULL, water_start, tr.ent, MASK_WATER); + + VectorAdd (water_start, tr.endpos, pos); + VectorScale (pos, 0.5, pos); + + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_BUBBLETRAIL); + gi.WritePosition (water_start); + gi.WritePosition (tr.endpos); + gi.multicast (pos, MULTICAST_PVS); + } +} + + +/* +================= +fire_bullet + +Fires a single round. Used for machinegun and chaingun. Would be fine for +pistols, rifles, etc.... +================= +*/ +void fire_bullet (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick, int hspread, int vspread, int mod) +{ + fire_lead (self, start, aimdir, damage, kick, TE_GUNSHOT, hspread, vspread, mod); +} + + +/* +================= +fire_shotgun + +Shoots shotgun pellets. Used by shotgun and super shotgun. +================= +*/ +void fire_shotgun (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick, int hspread, int vspread, int count, int mod) +{ + int i; + + for (i = 0; i < count; i++) + fire_lead (self, start, aimdir, damage, kick, TE_SHOTGUN, hspread, vspread, mod); +} + + +/* +================= +fire_blaster + +Fires a single blaster bolt. Used by the blaster and hyper blaster. +================= +*/ +void blaster_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + int mod; + + if (other == self->owner) + return; + + if (surf && (surf->flags & SURF_SKY)) + { + G_FreeEdict (self); + return; + } + + if (self->owner->client) + PlayerNoise(self->owner, self->s.origin, PNOISE_IMPACT); + + if (other->takedamage) + { + if (self->spawnflags & 1) + mod = MOD_HYPERBLASTER; + else + mod = MOD_BLASTER; + + T_Damage (other, self, self->owner, self->velocity, self->s.origin, plane->normal, self->dmg, 1, DAMAGE_ENERGY, mod); + } + else + { +//gi.bprintf(PRINT_HIGH,"%s\n",other->classname); + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_BLASTER); + gi.WritePosition (self->s.origin); + if (!plane) + gi.WriteDir (vec3_origin); + else + gi.WriteDir (plane->normal); + gi.multicast (self->s.origin, MULTICAST_PVS); + } + + G_FreeEdict (self); +} + +void fire_blaster (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int effect, qboolean hyper) +{ + edict_t *bolt; + trace_t tr; + + VectorNormalize (dir); + + bolt = G_Spawn(); + VectorCopy (start, bolt->s.origin); + VectorCopy (start, bolt->s.old_origin); + vectoangles (dir, bolt->s.angles); + VectorScale (dir, speed, bolt->velocity); + bolt->movetype = MOVETYPE_FLYMISSILE; + bolt->clipmask = MASK_SHOT; + bolt->solid = SOLID_BBOX; + bolt->s.effects |= effect; + VectorClear (bolt->mins); + VectorClear (bolt->maxs); + bolt->s.modelindex = gi.modelindex ("models/objects/laser/tris.md2"); + bolt->s.sound = gi.soundindex ("misc/lasfly.wav"); + bolt->owner = self; + bolt->touch = blaster_touch; + bolt->nextthink = level.time + 2; + bolt->think = G_FreeEdict; + bolt->dmg = damage; + bolt->classname = "bolt"; + if (hyper) + bolt->spawnflags = 1; + gi.linkentity (bolt); + + if (self->client) + check_dodge (self, bolt->s.origin, dir, speed); + + tr = gi.trace (self->s.origin, NULL, NULL, bolt->s.origin, bolt, MASK_SHOT); + if (tr.fraction < 1.0) + { + VectorMA (bolt->s.origin, -10, dir, bolt->s.origin); + bolt->touch (bolt, tr.ent, NULL, NULL); + } +} + + +/* +================= +fire_grenade +================= +*/ +static void Grenade_Explode (edict_t *ent) +{ + vec3_t origin; + int mod; + + if (ent->owner->client && !(ent->owner->svflags & SVF_DEADMONSTER)) + PlayerNoise(ent->owner, ent->s.origin, PNOISE_IMPACT); + + //FIXME: if we are onground then raise our Z just a bit since we are a point? + if (ent->enemy) + { + float points; + vec3_t v; + vec3_t dir; + + VectorAdd (ent->enemy->mins, ent->enemy->maxs, v); + VectorMA (ent->enemy->s.origin, 0.5, v, v); + VectorSubtract (ent->s.origin, v, v); + points = ent->dmg - 0.5 * VectorLength (v); + VectorSubtract (ent->enemy->s.origin, ent->s.origin, dir); + if (ent->spawnflags & 1) + mod = MOD_HANDGRENADE; + else + mod = MOD_GRENADE; + T_Damage (ent->enemy, ent, ent->owner, dir, ent->s.origin, vec3_origin, (int)points, (int)points, DAMAGE_RADIUS, mod); + } + + if (ent->spawnflags & 2) + mod = MOD_HELD_GRENADE; + else if (ent->spawnflags & 1) + mod = MOD_HG_SPLASH; + else + mod = MOD_G_SPLASH; + T_RadiusDamage(ent, ent->owner, ent->dmg, ent->enemy, ent->dmg_radius, mod); + + VectorMA (ent->s.origin, -0.02, ent->velocity, origin); + gi.WriteByte (svc_temp_entity); + if (ent->waterlevel) + { + if (ent->groundentity) + gi.WriteByte (TE_GRENADE_EXPLOSION_WATER); + else + gi.WriteByte (TE_ROCKET_EXPLOSION_WATER); + } + else + { + if (ent->groundentity) + gi.WriteByte (TE_GRENADE_EXPLOSION); + else + gi.WriteByte (TE_ROCKET_EXPLOSION); + } + gi.WritePosition (origin); + gi.multicast (ent->s.origin, MULTICAST_PHS); + + G_FreeEdict (ent); +UpdateExplIndex(NULL); +} + +static void Grenade_Touch (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + ent->enemy = NULL; + + if (other == ent->owner) + return; + + if (surf && (surf->flags & SURF_SKY)) + { + G_FreeEdict (ent); +UpdateExplIndex(NULL); + return; + } + + if (!other->takedamage) + { + if (ent->spawnflags & 1) + { + if (random() > 0.5) + gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/hgrenb1a.wav"), 1, ATTN_NORM, 0); + else + gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/hgrenb2a.wav"), 1, ATTN_NORM, 0); + } + else + { + gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/grenlb1b.wav"), 1, ATTN_NORM, 0); + } + return; + } + + ent->enemy = other; + Grenade_Explode (ent); +} + +void fire_grenade (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, float timer, float damage_radius) +{ + edict_t *grenade; + vec3_t dir; + vec3_t forward, right, up; + + vectoangles (aimdir, dir); + AngleVectors (dir, forward, right, up); + + grenade = G_Spawn(); + VectorCopy (start, grenade->s.origin); + VectorScale (aimdir, speed, grenade->velocity); + VectorMA (grenade->velocity, 200 + crandom() * 10.0, up, grenade->velocity); + VectorMA (grenade->velocity, crandom() * 10.0, right, grenade->velocity); + VectorSet (grenade->avelocity, 300, 300, 300); + grenade->movetype = MOVETYPE_BOUNCE; + grenade->clipmask = MASK_SHOT; + grenade->solid = SOLID_BBOX; + grenade->s.effects |= EF_GRENADE; + VectorClear (grenade->mins); + VectorClear (grenade->maxs); + grenade->s.modelindex = gi.modelindex ("models/objects/grenade/tris.md2"); + grenade->owner = self; + grenade->touch = Grenade_Touch; + grenade->nextthink = level.time + timer; + grenade->think = Grenade_Explode; + grenade->dmg = damage; + grenade->dmg_radius = damage_radius; + grenade->classname = "grenade"; + +UpdateExplIndex(grenade); + + gi.linkentity (grenade); +} + +void fire_grenade2 (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, float timer, float damage_radius, qboolean held) +{ + edict_t *grenade; + vec3_t dir; + vec3_t forward, right, up; + + vectoangles (aimdir, dir); + AngleVectors (dir, forward, right, up); + + grenade = G_Spawn(); + VectorCopy (start, grenade->s.origin); + VectorScale (aimdir, speed, grenade->velocity); + VectorMA (grenade->velocity, 200 + crandom() * 10.0, up, grenade->velocity); + VectorMA (grenade->velocity, crandom() * 10.0, right, grenade->velocity); + VectorSet (grenade->avelocity, 300, 300, 300); + grenade->movetype = MOVETYPE_BOUNCE; + grenade->clipmask = MASK_SHOT; + grenade->solid = SOLID_BBOX; + grenade->s.effects |= EF_GRENADE; + VectorClear (grenade->mins); + VectorClear (grenade->maxs); + grenade->s.modelindex = gi.modelindex ("models/objects/grenade2/tris.md2"); + grenade->owner = self; + grenade->touch = Grenade_Touch; + grenade->nextthink = level.time + timer; + grenade->think = Grenade_Explode; + grenade->dmg = damage; + grenade->dmg_radius = damage_radius; + grenade->classname = "hgrenade"; + if (held) + grenade->spawnflags = 3; + else + grenade->spawnflags = 1; + grenade->s.sound = gi.soundindex("weapons/hgrenc1b.wav"); + + if (timer <= 0.0) + Grenade_Explode (grenade); + else + { + gi.sound (self, CHAN_WEAPON, gi.soundindex ("weapons/hgrent1a.wav"), 1, ATTN_NORM, 0); + gi.linkentity (grenade); + } +} + +/* +================= +fire_rocket +================= +*/ +void rocket_touch (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + vec3_t origin; + int n; + + if (other == ent->owner) + return; + + if (surf && (surf->flags & SURF_SKY)) + { + G_FreeEdict (ent); +UpdateExplIndex(NULL); + return; + } + + if (ent->owner->client) + PlayerNoise(ent->owner, ent->s.origin, PNOISE_IMPACT); + + // calculate position for the explosion entity + VectorMA (ent->s.origin, -0.02, ent->velocity, origin); + + if (other->takedamage) + { + T_Damage (other, ent, ent->owner, ent->velocity, ent->s.origin, plane->normal, ent->dmg, 0, 0, MOD_ROCKET); + } + else + { + // don't throw any debris in net games + if (!deathmatch->value && !coop->value) + { + if ((surf) && !(surf->flags & (SURF_WARP|SURF_TRANS33|SURF_TRANS66|SURF_FLOWING))) + { + n = rand() % 5; + while(n--) + ThrowDebris (ent, "models/objects/debris2/tris.md2", 2, ent->s.origin); + } + } + } + + T_RadiusDamage(ent, ent->owner, ent->radius_dmg, other, ent->dmg_radius, MOD_R_SPLASH); + + if(Q_stricmp (ent->classname, "lockon rocket") == 0) + gi.sound (ent, CHAN_AUTO, gi.soundindex("3zb/locrexp.wav"), 1, ATTN_NONE, 0); + + gi.WriteByte (svc_temp_entity); + if (ent->waterlevel) + gi.WriteByte (TE_ROCKET_EXPLOSION_WATER); + else + gi.WriteByte (TE_ROCKET_EXPLOSION); + gi.WritePosition (origin); + gi.multicast (ent->s.origin, MULTICAST_PHS); + + G_FreeEdict (ent); +UpdateExplIndex(NULL); +} + +void fire_rocket (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, float damage_radius, int radius_damage) +{ + edict_t *rocket; + + rocket = G_Spawn(); + VectorCopy (start, rocket->s.origin); + VectorCopy (dir, rocket->movedir); + vectoangles (dir, rocket->s.angles); + VectorScale (dir, speed, rocket->velocity); + rocket->movetype = MOVETYPE_FLYMISSILE; + rocket->clipmask = MASK_SHOT; + rocket->solid = SOLID_BBOX; + rocket->s.effects |= EF_ROCKET; + VectorClear (rocket->mins); + VectorClear (rocket->maxs); + rocket->s.modelindex = gi.modelindex ("models/objects/rocket/tris.md2"); + rocket->owner = self; + rocket->touch = rocket_touch; + rocket->nextthink = level.time + 8000/speed; + rocket->think = G_FreeEdict; + rocket->dmg = damage; + rocket->radius_dmg = radius_damage; + rocket->dmg_radius = damage_radius; + rocket->s.sound = gi.soundindex ("weapons/rockfly.wav"); + rocket->classname = "rocket"; + +UpdateExplIndex(rocket); + + if (self->client) + check_dodge (self, rocket->s.origin, dir, speed); + + gi.linkentity (rocket); +} +//ロックオンミサイル +void think_lockon_rocket(edict_t *ent) +{ + vec3_t v; + + if(ent->moveinfo.speed < 100) + { + ent->s.sound = gi.soundindex ("3zb/locrfly.wav"); + ent->moveinfo.speed = 100; + } + + if(ent->moveinfo.speed < 1600) ent->moveinfo.speed *= 2; + + if(ent->target_ent) + { + if(!ent->target_ent->inuse || ent->target_ent->deadflag) + { + ent->target_ent = NULL; + ent->movetype = MOVETYPE_BOUNCE; + ent->touch = Grenade_Touch; + ent->think = Grenade_Explode; + ent->nextthink = level.time + FRAMETIME * 15; + ent->s.sound = 0; + + VectorCopy(ent->velocity,v); + VectorNormalize(v); + VectorScale (v, ent->moveinfo.speed, ent->velocity); + return; + } + else + { + VectorSubtract(ent->target_ent->s.origin,ent->s.origin,v); + VectorNormalize(v); + vectoangles (v, ent->s.angles); + VectorScale (v, ent->moveinfo.speed, ent->velocity); + ent->nextthink = level.time + FRAMETIME ;//* 2.0; + } + } + else + { + ent->movetype = MOVETYPE_BOUNCE; + ent->touch = Grenade_Touch; + ent->think = Grenade_Explode; + ent->nextthink = level.time + FRAMETIME * 15; + ent->s.sound = 0; + + VectorCopy(ent->velocity,v); + VectorNormalize(v); + VectorScale (v, ent->moveinfo.speed, ent->velocity); + return; + } + + //時間切れ + if(ent->moveinfo.accel <= level.time) + { + T_RadiusDamage(ent, ent->owner, ent->radius_dmg, NULL, ent->dmg_radius, MOD_R_SPLASH); + + gi.sound (ent, CHAN_AUTO, gi.soundindex("3zb/locrexp.wav"), 1, ATTN_NONE, 0); + gi.WriteByte (svc_temp_entity); + if (ent->waterlevel) + gi.WriteByte (TE_ROCKET_EXPLOSION_WATER); + else + gi.WriteByte (TE_ROCKET_EXPLOSION); + gi.WritePosition (ent->s.origin); + gi.multicast (ent->s.origin, MULTICAST_PHS); + + G_FreeEdict (ent); + } +} +void fire_lockon_rocket (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, float damage_radius, int radius_damage) +{ + edict_t *rocket; + + rocket = G_Spawn(); + VectorCopy (start, rocket->s.origin); + VectorCopy (dir, rocket->movedir); + vectoangles (dir, rocket->s.angles); + VectorScale (dir, speed, rocket->velocity); + + rocket->moveinfo.speed = speed; + + rocket->movetype = MOVETYPE_FLYMISSILE; + rocket->clipmask = MASK_SHOT; + rocket->solid = SOLID_BBOX; + rocket->s.effects |= EF_ROCKET; + VectorClear (rocket->mins); + VectorClear (rocket->maxs); + rocket->s.modelindex = gi.modelindex ("models/objects/rocket/tris.md2"); + rocket->owner = self; + rocket->touch = rocket_touch; + + rocket->nextthink = level.time + FRAMETIME * 8; + + rocket->moveinfo.accel = level.time + FRAMETIME * 36; + + rocket->think = think_lockon_rocket; + rocket->dmg = damage; + rocket->radius_dmg = radius_damage; + rocket->dmg_radius = damage_radius; + rocket->s.sound = gi.soundindex ("weapons/rockfly.wav"); + rocket->classname = "lockon rocket"; + + rocket->target_ent = self->client->zc.first_target; + + if (self->client) + check_dodge (self, rocket->s.origin, dir, speed); + + gi.linkentity (rocket); +} + +/* +================= +fire_rail +================= +*/ +void fire_rail (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick) +{ + vec3_t from; + vec3_t end; + trace_t tr; + edict_t *ignore; + int mask; + qboolean water; + + VectorMA (start, 8192, aimdir, end); + VectorCopy (start, from); + ignore = self; + water = false; + mask = MASK_SHOT|CONTENTS_SLIME|CONTENTS_LAVA; + while (ignore) + { + tr = gi.trace (from, NULL, NULL, end, ignore, mask); + + if (tr.contents & (CONTENTS_SLIME|CONTENTS_LAVA)) + { + mask &= ~(CONTENTS_SLIME|CONTENTS_LAVA); + water = true; + } + else + { + if ((tr.ent->svflags & SVF_MONSTER) || (tr.ent->client)) + ignore = tr.ent; + else + ignore = NULL; + + if ((tr.ent != self) && (tr.ent->takedamage)) + T_Damage (tr.ent, self, self, aimdir, tr.endpos, tr.plane.normal, damage, kick, 0, MOD_RAILGUN); + } + + VectorCopy (tr.endpos, from); + } + + // send gun puff / flash + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_RAILTRAIL); + gi.WritePosition (start); + gi.WritePosition (tr.endpos); + gi.multicast (self->s.origin, MULTICAST_PHS); +// gi.multicast (start, MULTICAST_PHS); + if (water) + { + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_RAILTRAIL); + gi.WritePosition (start); + gi.WritePosition (tr.endpos); + gi.multicast (tr.endpos, MULTICAST_PHS); + } + + if (self->client) + PlayerNoise(self, tr.endpos, PNOISE_IMPACT); +} + +void fire_sniperail (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick) +{ + vec3_t from; + vec3_t end; + trace_t tr; + edict_t *ignore; + int mask; + qboolean water; + + VectorMA (start, 8192, aimdir, end); + VectorCopy (start, from); + ignore = self; + water = false; + mask = MASK_SHOT|CONTENTS_SLIME|CONTENTS_LAVA; + while (ignore) + { + tr = gi.trace (from, NULL, NULL, end, ignore, mask); + + if (tr.contents & (CONTENTS_SLIME|CONTENTS_LAVA)) + { + mask &= ~(CONTENTS_SLIME|CONTENTS_LAVA); + water = true; + } + else + { + if ((tr.ent->svflags & SVF_MONSTER) || (tr.ent->client)) + ignore = tr.ent; + else + ignore = NULL; + + if ((tr.ent != self) && (tr.ent->takedamage)) + T_Damage (tr.ent, self, self, aimdir, tr.endpos, tr.plane.normal, damage, kick, 0, MOD_RAILGUN); + } + + VectorCopy (tr.endpos, from); + } + + VectorScale (aimdir,100, from); + VectorSubtract(tr.endpos,from,start); + +// gi.bprintf(PRINT_HIGH,"jj\n"); + + // send gun puff / flash + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_RAILTRAIL); + gi.WritePosition (start); + gi.WritePosition (tr.endpos); + gi.multicast (self->s.origin, MULTICAST_PHS); +// gi.multicast (start, MULTICAST_PHS); + if (water) + { + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_RAILTRAIL); + gi.WritePosition (start); + gi.WritePosition (tr.endpos); + gi.multicast (tr.endpos, MULTICAST_PHS); + } + + if (self->client) + PlayerNoise(self, tr.endpos, PNOISE_IMPACT); +} + +/* +================= +fire_bfg +================= +*/ +void bfg_explode (edict_t *self) +{ + edict_t *ent; + float points; + vec3_t v; + float dist; + + if (self->s.frame == 0) + { + // the BFG effect + ent = NULL; + while ((ent = findradius(ent, self->s.origin, self->dmg_radius)) != NULL) + { + if (!ent->takedamage) + continue; + if (ent == self->owner) + continue; + if (!CanDamage (ent, self)) + continue; + if (!CanDamage (ent, self->owner)) + continue; + + VectorAdd (ent->mins, ent->maxs, v); + VectorMA (ent->s.origin, 0.5, v, v); + VectorSubtract (self->s.origin, v, v); + dist = VectorLength(v); + points = self->radius_dmg * (1.0 - sqrt(dist/self->dmg_radius)); + if (ent == self->owner) + points = points * 0.5; + + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_BFG_EXPLOSION); + gi.WritePosition (ent->s.origin); + gi.multicast (ent->s.origin, MULTICAST_PHS); + T_Damage (ent, self, self->owner, self->velocity, ent->s.origin, vec3_origin, (int)points, 0, DAMAGE_ENERGY, MOD_BFG_EFFECT); + } + } + + self->nextthink = level.time + FRAMETIME; + self->s.frame++; + if (self->s.frame == 5) + self->think = G_FreeEdict; +} + +void bfg_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + if (other == self->owner) + return; + + if (surf && (surf->flags & SURF_SKY)) + { + G_FreeEdict (self); + return; + } + + if (self->owner->client) + PlayerNoise(self->owner, self->s.origin, PNOISE_IMPACT); + + // core explosion - prevents firing it into the wall/floor + if (other->takedamage) + T_Damage (other, self, self->owner, self->velocity, self->s.origin, plane->normal, 200, 0, 0, MOD_BFG_BLAST); + T_RadiusDamage(self, self->owner, 200, other, 100, MOD_BFG_BLAST); + + gi.sound (self, CHAN_VOICE, gi.soundindex ("weapons/bfg__x1b.wav"), 1, ATTN_NORM, 0); + self->solid = SOLID_NOT; + self->touch = NULL; + VectorMA (self->s.origin, -1 * FRAMETIME, self->velocity, self->s.origin); + VectorClear (self->velocity); + self->s.modelindex = gi.modelindex ("sprites/s_bfg3.sp2"); + self->s.frame = 0; + self->s.sound = 0; + self->s.effects &= ~EF_ANIM_ALLFAST; + self->think = bfg_explode; + self->nextthink = level.time + FRAMETIME; + self->enemy = other; + + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_BFG_BIGEXPLOSION); + gi.WritePosition (self->s.origin); + gi.multicast (self->s.origin, MULTICAST_PVS); +} + + +void bfg_think (edict_t *self) +{ + edict_t *ent; + edict_t *ignore; + vec3_t point; + vec3_t dir; + vec3_t start; + vec3_t end; + int dmg; + trace_t tr; + + if (deathmatch->value) + dmg = 5; + else + dmg = 10; + + ent = NULL; + while ((ent = findradius(ent, self->s.origin, 256)) != NULL) + { + if (ent == self) + continue; + + if (ent == self->owner) + continue; + + if (!ent->takedamage) + continue; + + if (!(ent->svflags & SVF_MONSTER) && (!ent->client) && (strcmp(ent->classname, "misc_explobox") != 0)) + continue; + +//ZOID + //don't target players in CTF + if (ctf->value && ent->client && + self->owner->client && + ent->client->resp.ctf_team == self->owner->client->resp.ctf_team) + continue; +//ZOID + + VectorMA (ent->absmin, 0.5, ent->size, point); + + VectorSubtract (point, self->s.origin, dir); + VectorNormalize (dir); + + ignore = self; + VectorCopy (self->s.origin, start); + VectorMA (start, 2048, dir, end); + while(1) + { + tr = gi.trace (start, NULL, NULL, end, ignore, CONTENTS_SOLID|CONTENTS_MONSTER|CONTENTS_DEADMONSTER); + + if (!tr.ent) + break; + + // hurt it if we can + if ((tr.ent->takedamage) && !(tr.ent->flags & FL_IMMUNE_LASER) && (tr.ent != self->owner)) + T_Damage (tr.ent, self, self->owner, dir, tr.endpos, vec3_origin, dmg, 1, DAMAGE_ENERGY, MOD_BFG_LASER); + + // if we hit something that's not a monster or player we're done + if (!(tr.ent->svflags & SVF_MONSTER) && (!tr.ent->client)) + { + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_LASER_SPARKS); + gi.WriteByte (4); + gi.WritePosition (tr.endpos); + gi.WriteDir (tr.plane.normal); + gi.WriteByte (self->s.skinnum); + gi.multicast (tr.endpos, MULTICAST_PVS); + break; + } + + ignore = tr.ent; + VectorCopy (tr.endpos, start); + } + + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_BFG_LASER); + gi.WritePosition (self->s.origin); + gi.WritePosition (tr.endpos); + gi.multicast (self->s.origin, MULTICAST_PHS); + } + + self->nextthink = level.time + FRAMETIME; +} + + +void fire_bfg (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, float damage_radius) +{ + edict_t *bfg; + + bfg = G_Spawn(); + VectorCopy (start, bfg->s.origin); + VectorCopy (dir, bfg->movedir); + vectoangles (dir, bfg->s.angles); + VectorScale (dir, speed, bfg->velocity); + bfg->movetype = MOVETYPE_FLYMISSILE; + bfg->clipmask = MASK_SHOT; + bfg->solid = SOLID_BBOX; + bfg->s.effects |= EF_BFG | EF_ANIM_ALLFAST; + VectorClear (bfg->mins); + VectorClear (bfg->maxs); + bfg->s.modelindex = gi.modelindex ("sprites/s_bfg1.sp2"); + bfg->owner = self; + bfg->touch = bfg_touch; + bfg->nextthink = level.time + 8000/speed; + bfg->think = G_FreeEdict; + bfg->radius_dmg = damage; + bfg->dmg_radius = damage_radius; + bfg->classname = "bfg blast"; + bfg->s.sound = gi.soundindex ("weapons/bfg__l1a.wav"); + + bfg->think = bfg_think; + bfg->nextthink = level.time + FRAMETIME; + bfg->teammaster = bfg; + bfg->teamchain = NULL; + + if (self->client) + check_dodge (self, bfg->s.origin, dir, speed); + + gi.linkentity (bfg); +} + +/*===============================================================================*/ + +// RAFAEL +/* +================= + fire_ionripper +================= +*/ + +void ionripper_sparks (edict_t *self) +{ + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_WELDING_SPARKS); + gi.WriteByte (0); + gi.WritePosition (self->s.origin); + gi.WriteDir (vec3_origin); + gi.WriteByte (0xe4 + (rand()&3)); + gi.multicast (self->s.origin, MULTICAST_PVS); + + G_FreeEdict (self); +} + +// RAFAEL +void ionripper_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + if (other == self->owner) + return; + + if (surf && (surf->flags & SURF_SKY)) + { + G_FreeEdict (self); + return; + } + + if (self->owner->client) + PlayerNoise (self->owner, self->s.origin, PNOISE_IMPACT); + + if (other->takedamage) + { + T_Damage (other, self, self->owner, self->velocity, self->s.origin, plane->normal, self->dmg, 1, DAMAGE_ENERGY, MOD_RIPPER); + + } + else + { + return; + } + + G_FreeEdict (self); +} + + +// RAFAEL +void fire_ionripper (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int effect) +{ + edict_t *ion; + trace_t tr; + + VectorNormalize (dir); + + ion = G_Spawn (); + VectorCopy (start, ion->s.origin); + VectorCopy (start, ion->s.old_origin); + vectoangles (dir, ion->s.angles); + VectorScale (dir, speed, ion->velocity); + + ion->movetype = MOVETYPE_WALLBOUNCE; + ion->clipmask = MASK_SHOT; + ion->solid = SOLID_BBOX; + ion->s.effects |= effect; + + ion->s.renderfx |= RF_FULLBRIGHT; + + VectorClear (ion->mins); + VectorClear (ion->maxs); + ion->s.modelindex = gi.modelindex ("models/objects/boomrang/tris.md2"); + ion->s.sound = gi.soundindex ("misc/lasfly.wav"); + ion->owner = self; + ion->touch = ionripper_touch; + ion->nextthink = level.time + 3; + ion->think = ionripper_sparks; + ion->dmg = damage; + ion->dmg_radius = 100; + gi.linkentity (ion); + + if (self->client) + check_dodge (self, ion->s.origin, dir, speed); + + tr = gi.trace (self->s.origin, NULL, NULL, ion->s.origin, ion, MASK_SHOT); + if (tr.fraction < 1.0) + { + VectorMA (ion->s.origin, -10, dir, ion->s.origin); + ion->touch (ion, tr.ent, NULL, NULL); + } + +} + + +// RAFAEL +/* +================= +fire_heat +================= +*/ +/* +void heat_think (edict_t *self) +{ + edict_t *target = NULL; + edict_t *aquire = NULL; + vec3_t vec; + vec3_t oldang; + int len; + int oldlen = 0; + + VectorClear (vec); + + // aquire new target + while (( target = findradius (target, self->s.origin, 1024)) != NULL) + { + + if (self->owner == target) + continue; + if (!target->svflags & SVF_MONSTER) + continue; + if (!target->client) + continue; + if (target->health <= 0) + continue; + if (!visible (self, target)) + continue; + + // if we need to reduce the tracking cone + /* + { + vec3_t vec; + float dot; + vec3_t forward; + + AngleVectors (self->s.angles, forward, NULL, NULL); + VectorSubtract (target->s.origin, self->s.origin, vec); + VectorNormalize (vec); + dot = DotProduct (vec, forward); + + if (dot > 0.6) + continue; + } + */ + +/* if (!infront (self, target)) + continue; + + VectorSubtract (self->s.origin, target->s.origin, vec); + len = VectorLength (vec); + + if (aquire == NULL || len < oldlen) + { + aquire = target; + self->target_ent = aquire; + oldlen = len; + } + } + + if (aquire != NULL) + { + VectorCopy (self->s.angles, oldang); + VectorSubtract (aquire->s.origin, self->s.origin, vec); + + vectoangles (vec, self->s.angles); + + VectorNormalize (vec); + VectorCopy (vec, self->movedir); + VectorScale (vec, 500, self->velocity); + } + + self->nextthink = level.time + 0.1; +} +*/ +// RAFAEL +/* +void fire_heat (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, float damage_radius, int radius_damage) +{ + edict_t *heat; + + heat = G_Spawn(); + VectorCopy (start, heat->s.origin); + VectorCopy (dir, heat->movedir); + vectoangles (dir, heat->s.angles); + VectorScale (dir, speed, heat->velocity); + heat->movetype = MOVETYPE_FLYMISSILE; + heat->clipmask = MASK_SHOT; + heat->solid = SOLID_BBOX; + heat->s.effects |= EF_ROCKET; + VectorClear (heat->mins); + VectorClear (heat->maxs); + heat->s.modelindex = gi.modelindex ("models/objects/rocket/tris.md2"); + heat->owner = self; + heat->touch = rocket_touch; + + heat->nextthink = level.time + 0.1; + heat->think = heat_think; + + heat->dmg = damage; + heat->radius_dmg = radius_damage; + heat->dmg_radius = damage_radius; + heat->s.sound = gi.soundindex ("weapons/rockfly.wav"); + + if (self->client) + check_dodge (self, heat->s.origin, dir, speed); + + gi.linkentity (heat); +} +*/ + + +// RAFAEL +/* +================= + fire_plasma +================= +*/ + +void plasma_touch (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + vec3_t origin; + + if (other == ent->owner) + return; + + if (surf && (surf->flags & SURF_SKY)) + { + G_FreeEdict (ent); + return; + } + + if (ent->owner->client) + PlayerNoise(ent->owner, ent->s.origin, PNOISE_IMPACT); + + // calculate position for the explosion entity + VectorMA (ent->s.origin, -0.02, ent->velocity, origin); + + if (other->takedamage) + { + T_Damage (other, ent, ent->owner, ent->velocity, ent->s.origin, plane->normal, ent->dmg, 0, 0, MOD_PHALANX); + } + + T_RadiusDamage(ent, ent->owner, ent->radius_dmg, other, ent->dmg_radius, MOD_PHALANX); + + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_PLASMA_EXPLOSION); + gi.WritePosition (origin); + gi.multicast (ent->s.origin, MULTICAST_PVS); + + G_FreeEdict (ent); +} + + +// RAFAEL +void fire_plasma (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, float damage_radius, int radius_damage) +{ + edict_t *plasma; + + plasma = G_Spawn(); + VectorCopy (start, plasma->s.origin); + VectorCopy (dir, plasma->movedir); + vectoangles (dir, plasma->s.angles); + VectorScale (dir, speed, plasma->velocity); + plasma->movetype = MOVETYPE_FLYMISSILE; + plasma->clipmask = MASK_SHOT; + plasma->solid = SOLID_BBOX; + + VectorClear (plasma->mins); + VectorClear (plasma->maxs); + + plasma->owner = self; + plasma->touch = plasma_touch; + plasma->nextthink = level.time + 8000/speed; + plasma->think = G_FreeEdict; + plasma->dmg = damage; + plasma->radius_dmg = radius_damage; + plasma->dmg_radius = damage_radius; + plasma->s.sound = gi.soundindex ("weapons/rockfly.wav"); + + plasma->s.modelindex = gi.modelindex ("sprites/s_photon.sp2"); + plasma->s.effects |= EF_PLASMA | EF_ANIM_ALLFAST; + + if (self->client) + check_dodge (self, plasma->s.origin, dir, speed); + + gi.linkentity (plasma); + + +} + + +/* +================= +trap +================= +*/ + +// RAFAEL +extern void SP_item_foodcube (edict_t *best); +// RAFAEL +static void Trap_Think (edict_t *ent) +{ + edict_t *target = NULL; + edict_t *best = NULL; + vec3_t vec; + int len, i; + int oldlen = 8000; + vec3_t forward, right, up; + + if (ent->timestamp < level.time) + { + BecomeExplosion1(ent); + // note to self + // cause explosion damage??? + return; + } + + ent->nextthink = level.time + 0.1; + + if (!ent->groundentity) + return; + + // ok lets do the blood effect + if (ent->s.frame > 4) + { + if (ent->s.frame == 5) + { + if (ent->wait == 64) + gi.sound(ent, CHAN_VOICE, gi.soundindex ("weapons/trapdown.wav"), 1, ATTN_IDLE, 0); + + ent->wait -= 2; + ent->delay += level.time; + + for (i=0; i<3; i++) + { + + best = G_Spawn(); + + if (strcmp (ent->enemy->classname, "monster_gekk") == 0) + { + best->s.modelindex = gi.modelindex ("models/objects/gekkgib/torso/tris.md2"); + best->s.effects |= TE_GREENBLOOD; + } + else if (ent->mass > 200) + { + best->s.modelindex = gi.modelindex ("models/objects/gibs/chest/tris.md2"); + best->s.effects |= TE_BLOOD; + } + else + { + best->s.modelindex = gi.modelindex ("models/objects/gibs/sm_meat/tris.md2"); + best->s.effects |= TE_BLOOD; + } + + AngleVectors (ent->s.angles, forward, right, up); + + RotatePointAroundVector( vec, up, right, ((360.0/3)* i)+ent->delay); + VectorMA (vec, ent->wait/2, vec, vec); + VectorAdd(vec, ent->s.origin, vec); + VectorAdd(vec, forward, best->s.origin); + + best->s.origin[2] = ent->s.origin[2] + ent->wait; + + VectorCopy (ent->s.angles, best->s.angles); + + best->solid = SOLID_NOT; + best->s.effects |= EF_GIB; + best->takedamage = DAMAGE_YES; + + best->movetype = MOVETYPE_TOSS; + best->svflags |= SVF_MONSTER; + best->deadflag = DEAD_DEAD; + + VectorClear (best->mins); + VectorClear (best->maxs); + + best->watertype = gi.pointcontents(best->s.origin); + if (best->watertype & MASK_WATER) + best->waterlevel = 1; + + best->nextthink = level.time + 0.1; + best->think = G_FreeEdict; + gi.linkentity (best); + } + + if (ent->wait < 19) + ent->s.frame ++; + + return; + } + ent->s.frame ++; + if (ent->s.frame == 8) + { + ent->nextthink = level.time + 1.0; + ent->think = G_FreeEdict; + + best = G_Spawn (); + SP_item_foodcube (best); + VectorCopy (ent->s.origin, best->s.origin); + best->s.origin[2]+= 16; + best->velocity[2] = 400; + best->count = ent->mass; + gi.linkentity (best); + return; + } + return; + } + + ent->s.effects &= ~EF_TRAP; + if (ent->s.frame >= 4) + { + ent->s.effects |= EF_TRAP; + VectorClear (ent->mins); + VectorClear (ent->maxs); + + } + + if (ent->s.frame < 4) + ent->s.frame++; + + while ((target = findradius(target, ent->s.origin, 256)) != NULL) + { + if (target == ent) + continue; + if (!(target->svflags & SVF_MONSTER) && !target->client) + continue; + // if (target == ent->owner) + // continue; + if (target->health <= 0) + continue; + if (!visible (ent, target)) + continue; + if (!best) + { + best = target; + continue; + } + VectorSubtract (ent->s.origin, target->s.origin, vec); + len = VectorLength (vec); + if (len < oldlen) + { + oldlen = len; + best = target; + } + } + + // pull the enemy in + if (best) + { + vec3_t forward; + + if (best->groundentity) + { + best->s.origin[2] += 1; + best->groundentity = NULL; + } + VectorSubtract (ent->s.origin, best->s.origin, vec); + len = VectorLength (vec); + if (best->client) + { + VectorNormalize (vec); + VectorMA (best->velocity, 250, vec, best->velocity); + if(best->svflags & SVF_MONSTER) + { + //if(best->velocity[2] < 100) best->velocity[2] = 100; + best->client->zc.trapped = true; + } + } + else + { + best->ideal_yaw = vectoyaw(vec); +// M_ChangeYaw (best); + AngleVectors (best->s.angles, forward, NULL, NULL); + VectorScale (forward, 256, best->velocity); + } + + gi.sound(ent, CHAN_VOICE, gi.soundindex ("weapons/trapsuck.wav"), 1, ATTN_IDLE, 0); + + if (len < 32) + { + if (best->mass < 400) + { + T_Damage (best, ent, ent->owner, vec3_origin, best->s.origin, vec3_origin, 100000, 1, 0, MOD_TRAP); + ent->enemy = best; + ent->wait = 64; + VectorCopy (ent->s.origin, ent->s.old_origin); + ent->timestamp = level.time + 30; + if (deathmatch->value) + ent->mass = best->mass/4; + else + ent->mass = best->mass/10; + // ok spawn the food cube + ent->s.frame = 5; + } + else + { + BecomeExplosion1(ent); + // note to self + // cause explosion damage??? + return; + } + + } + } + + +} + + +// RAFAEL +void fire_trap (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, float timer, float damage_radius, qboolean held) +{ + edict_t *trap; + vec3_t dir; + vec3_t forward, right, up; + + vectoangles (aimdir, dir); + AngleVectors (dir, forward, right, up); + + trap = G_Spawn(); + VectorCopy (start, trap->s.origin); + VectorScale (aimdir, speed, trap->velocity); + VectorMA (trap->velocity, 200 + crandom() * 10.0, up, trap->velocity); + VectorMA (trap->velocity, crandom() * 10.0, right, trap->velocity); + VectorSet (trap->avelocity, 0, 300, 0); + trap->movetype = MOVETYPE_BOUNCE; + trap->clipmask = MASK_SHOT; + trap->solid = SOLID_BBOX; +// VectorClear (trap->mins); +// VectorClear (trap->maxs); + VectorSet (trap->mins, -4, -4, 0); + VectorSet (trap->maxs, 4, 4, 8); + trap->s.modelindex = gi.modelindex ("models/weapons/z_trap/tris.md2"); + trap->owner = self; + trap->nextthink = level.time + 1.0; + trap->think = Trap_Think; + trap->dmg = damage; + trap->dmg_radius = damage_radius; + trap->classname = "htrap"; + // RAFAEL 16-APR-98 + trap->s.sound = gi.soundindex ("weapons/traploop.wav"); + // END 16-APR-98 + if (held) + trap->spawnflags = 3; + else + trap->spawnflags = 1; + + if (timer <= 0.0) + Grenade_Explode (trap); + else + { + // gi.sound (self, CHAN_WEAPON, gi.soundindex ("weapons/trapdown.wav"), 1, ATTN_NORM, 0); + gi.linkentity (trap); + } + + trap->timestamp = level.time + 30; + +} + diff --git a/src/game.h b/src/game.h new file mode 100644 index 0000000..041b13e --- /dev/null +++ b/src/game.h @@ -0,0 +1,220 @@ +#ifndef GAMEHEAD +#define GAMEHEAD +// game.h -- game dll information visible to server + +#define GAME_API_VERSION 3 + +// edict->svflags + +#define SVF_NOCLIENT 0x00000001 // don't send entity to clients, even if it has effects +#define SVF_DEADMONSTER 0x00000002 // treat as CONTENTS_DEADMONSTER for collision +#define SVF_MONSTER 0x00000004 // treat as CONTENTS_MONSTER for collision + +// edict->solid values + +typedef enum +{ +SOLID_NOT, // no interaction with other objects +SOLID_TRIGGER, // only touch when inside, after moving +SOLID_BBOX, // touch on edge +SOLID_BSP // bsp clip, touch on edge +} solid_t; + +//=============================================================== + +// link_t is only used for entity area links now +typedef struct link_s +{ + struct link_s *prev, *next; +} link_t; + +#define MAX_ENT_CLUSTERS 16 + + +typedef struct edict_s edict_t; +typedef struct gclient_s gclient_t; + + +#ifndef GAME_INCLUDE + +typedef struct gclient_s +{ + player_state_t ps; // communicated by server to clients + int ping; + // the game dll can add anything it wants after + // this point in the structure +} gclient_t; + + +struct edict_s +{ + entity_state_t s; + struct gclient_s *client; + qboolean inuse; + int linkcount; + + // FIXME: move these fields to a server private sv_entity_t + link_t area; // linked to a division node or leaf + + int num_clusters; // if -1, use headnode instead + int clusternums[MAX_ENT_CLUSTERS]; + int headnode; // unused if num_clusters != -1 + int areanum, areanum2; + + //================================ + + int svflags; // SVF_NOCLIENT, SVF_DEADMONSTER, SVF_MONSTER, etc + vec3_t mins, maxs; + vec3_t absmin, absmax, size; + solid_t solid; + int clipmask; + edict_t *owner; + + // the game dll can add anything it wants after + // this point in the structure +}; + +#endif // GAME_INCLUDE + +//=============================================================== + +// +// functions provided by the main engine +// +typedef struct +{ + // special messages + void (*bprintf) (int printlevel, char *fmt, ...); + void (*dprintf) (char *fmt, ...); + void (*cprintf) (edict_t *ent, int printlevel, char *fmt, ...); + void (*centerprintf) (edict_t *ent, char *fmt, ...); + void (*sound) (edict_t *ent, int channel, int soundindex, float volume, float attenuation, float timeofs); + void (*positioned_sound) (vec3_t origin, edict_t *ent, int channel, int soundinedex, float volume, float attenuation, float timeofs); + + // config strings hold all the index strings, the lightstyles, + // and misc data like the sky definition and cdtrack. + // All of the current configstrings are sent to clients when + // they connect, and changes are sent to all connected clients. + void (*configstring) (int num, char *string); + + void (*error) (char *fmt, ...); + + // new names can only be added during spawning + // existing names can be looked up at any time + int (*modelindex) (char *name); + int (*soundindex) (char *name); + int (*imageindex) (char *name); + + void (*setmodel) (edict_t *ent, char *name); + + // collision detection + trace_t (*trace) (vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end, edict_t *passent, int contentmask); + int (*pointcontents) (vec3_t point); + qboolean (*inPVS) (vec3_t p1, vec3_t p2); + qboolean (*inPHS) (vec3_t p1, vec3_t p2); + void (*SetAreaPortalState) (int portalnum, qboolean open); + qboolean (*AreasConnected) (int area1, int area2); + + // an entity will never be sent to a client or used for collision + // if it is not passed to linkentity. If the size, position, or + // solidity changes, it must be relinked. + void (*linkentity) (edict_t *ent); + void (*unlinkentity) (edict_t *ent); // call before removing an interactive edict + int (*BoxEdicts) (vec3_t mins, vec3_t maxs, edict_t **list, int maxcount, int areatype); + void (*Pmove) (pmove_t *pmove); // player movement code common with client prediction + + // network messaging + void (*multicast) (vec3_t origin, multicast_t to); + void (*unicast) (edict_t *ent, qboolean reliable); + void (*WriteChar) (int c); + void (*WriteByte) (int c); + void (*WriteShort) (int c); + void (*WriteLong) (int c); + void (*WriteFloat) (float f); + void (*WriteString) (char *s); + void (*WritePosition) (vec3_t pos); // some fractional bits + void (*WriteDir) (vec3_t pos); // single byte encoded, very coarse + void (*WriteAngle) (float f); + + // managed memory allocation + void *(*TagMalloc) (int size, int tag); + void (*TagFree) (void *block); + void (*FreeTags) (int tag); + + // console variable interaction + cvar_t *(*cvar) (char *var_name, char *value, int flags); + cvar_t *(*cvar_set) (char *var_name, char *value); + cvar_t *(*cvar_forceset) (char *var_name, char *value); + + // ClientCommand and coneole command parameter checking + int (*argc) (void); + char *(*argv) (int n); + char *(*args) (void); + + // add commands to the server console as if they were typed in + // for map changing, etc + void (*AddCommandString) (char *text); + + void (*DebugGraph) (float value, int color); +} game_import_t; + +// +// functions exported by the game subsystem +// +typedef struct +{ + int apiversion; + + // the init function will only be called when a game starts, + // not each time a level is loaded. Persistant data for clients + // and the server can be allocated in init + void (*Init) (void); + void (*Shutdown) (void); + + // each new level entered will cause a call to SpawnEntities + void (*SpawnEntities) (char *mapname, char *entstring, char *spawnpoint); + + // Read/Write Game is for storing persistant cross level information + // about the world state and the clients. + // WriteGame is called every time a level is exited. + // ReadGame is called on a loadgame. + void (*WriteGame) (char *filename); + void (*ReadGame) (char *filename); + + // ReadLevel is called after the default map information has been + // loaded with SpawnEntities, so any stored client spawn spots will + // be used when the clients reconnect. + void (*WriteLevel) (char *filename); + void (*ReadLevel) (char *filename); + + qboolean (*ClientConnect) (edict_t *ent, char *userinfo, qboolean loadgame); + void (*ClientBegin) (edict_t *ent, qboolean loadgame); + void (*ClientUserinfoChanged) (edict_t *ent, char *userinfo); + void (*ClientDisconnect) (edict_t *ent); + void (*ClientCommand) (edict_t *ent); + void (*ClientThink) (edict_t *ent, usercmd_t *cmd); + + void (*RunFrame) (void); + + // ServerCommand will be called when an "sv " command is issued on the + // server console. + // The game can issue gi.argc() / gi.argv() commands to get the rest + // of the parameters + void (*ServerCommand) (void); + + // + // global variables shared between game and server + // + + // The edict array is allocated in the game dll so it + // can vary in size from one game to another. + // + // The size will be fixed when ge->Init() is called + struct edict_s *edicts; + int edict_size; + int num_edicts; // current number, <= max_edicts + int max_edicts; +} game_export_t; + +game_export_t *GetGameApi (game_import_t *import); +#endif \ No newline at end of file diff --git a/src/m_move.c b/src/m_move.c new file mode 100644 index 0000000..9c825b1 --- /dev/null +++ b/src/m_move.c @@ -0,0 +1,80 @@ +// m_move.c -- monster movement + +#include "g_local.h" + +#define STEPSIZE 18 + +/* +============= +M_CheckBottom + +Returns false if any part of the bottom of the entity is off an edge that +is not a staircase. + +============= +*/ +int c_yes, c_no; + +qboolean M_CheckBottom (edict_t *ent) +{ + vec3_t mins, maxs, start, stop; + trace_t trace; + int x, y; + float mid, bottom; + + VectorAdd (ent->s.origin, ent->mins, mins); + VectorAdd (ent->s.origin, ent->maxs, maxs); + +// if all of the points under the corners are solid world, don't bother +// with the tougher checks +// the corners must be within 16 of the midpoint + start[2] = mins[2] - 1; + for (x=0 ; x<=1 ; x++) + for (y=0 ; y<=1 ; y++) + { + start[0] = x ? maxs[0] : mins[0]; + start[1] = y ? maxs[1] : mins[1]; + if (gi.pointcontents (start) != CONTENTS_SOLID) + goto realcheck; + } + + c_yes++; + return true; // we got out easy + +realcheck: + c_no++; +// +// check it for real... +// + start[2] = mins[2]; + +// the midpoint must be within 16 of the bottom + start[0] = stop[0] = (mins[0] + maxs[0])*0.5; + start[1] = stop[1] = (mins[1] + maxs[1])*0.5; + stop[2] = start[2] - 2*STEPSIZE; + trace = gi.trace (start, vec3_origin, vec3_origin, stop, ent,MASK_PLAYERSOLID /*MASK_MONSTERSOLID*/); + + if (trace.fraction == 1.0) + return false; + mid = bottom = trace.endpos[2]; + +// the corners must be within 16 of the midpoint + for (x=0 ; x<=1 ; x++) + for (y=0 ; y<=1 ; y++) + { + start[0] = stop[0] = x ? maxs[0] : mins[0]; + start[1] = stop[1] = y ? maxs[1] : mins[1]; + + trace = gi.trace (start, vec3_origin, vec3_origin, stop, ent, MASK_PLAYERSOLID /*MASK_MONSTERSOLID*/); + + if (trace.fraction != 1.0 && trace.endpos[2] > bottom) + bottom = trace.endpos[2]; + if (trace.fraction == 1.0 || mid - trace.endpos[2] > STEPSIZE) + return false; + } + + c_yes++; + return true; +} + + diff --git a/src/m_player.h b/src/m_player.h new file mode 100644 index 0000000..e8d2beb --- /dev/null +++ b/src/m_player.h @@ -0,0 +1,205 @@ +// G:\quake2\baseq2\models/player_x/frames + +// This file generated by qdata - Do NOT Modify + +#define FRAME_stand01 0 +#define FRAME_stand02 1 +#define FRAME_stand03 2 +#define FRAME_stand04 3 +#define FRAME_stand05 4 +#define FRAME_stand06 5 +#define FRAME_stand07 6 +#define FRAME_stand08 7 +#define FRAME_stand09 8 +#define FRAME_stand10 9 +#define FRAME_stand11 10 +#define FRAME_stand12 11 +#define FRAME_stand13 12 +#define FRAME_stand14 13 +#define FRAME_stand15 14 +#define FRAME_stand16 15 +#define FRAME_stand17 16 +#define FRAME_stand18 17 +#define FRAME_stand19 18 +#define FRAME_stand20 19 +#define FRAME_stand21 20 +#define FRAME_stand22 21 +#define FRAME_stand23 22 +#define FRAME_stand24 23 +#define FRAME_stand25 24 +#define FRAME_stand26 25 +#define FRAME_stand27 26 +#define FRAME_stand28 27 +#define FRAME_stand29 28 +#define FRAME_stand30 29 +#define FRAME_stand31 30 +#define FRAME_stand32 31 +#define FRAME_stand33 32 +#define FRAME_stand34 33 +#define FRAME_stand35 34 +#define FRAME_stand36 35 +#define FRAME_stand37 36 +#define FRAME_stand38 37 +#define FRAME_stand39 38 +#define FRAME_stand40 39 +#define FRAME_run1 40 +#define FRAME_run2 41 +#define FRAME_run3 42 +#define FRAME_run4 43 +#define FRAME_run5 44 +#define FRAME_run6 45 +#define FRAME_attack1 46 +#define FRAME_attack2 47 +#define FRAME_attack3 48 +#define FRAME_attack4 49 +#define FRAME_attack5 50 +#define FRAME_attack6 51 +#define FRAME_attack7 52 +#define FRAME_attack8 53 +#define FRAME_pain101 54 +#define FRAME_pain102 55 +#define FRAME_pain103 56 +#define FRAME_pain104 57 +#define FRAME_pain201 58 +#define FRAME_pain202 59 +#define FRAME_pain203 60 +#define FRAME_pain204 61 +#define FRAME_pain301 62 +#define FRAME_pain302 63 +#define FRAME_pain303 64 +#define FRAME_pain304 65 +#define FRAME_jump1 66 +#define FRAME_jump2 67 +#define FRAME_jump3 68 +#define FRAME_jump4 69 +#define FRAME_jump5 70 +#define FRAME_jump6 71 +#define FRAME_flip01 72 +#define FRAME_flip02 73 +#define FRAME_flip03 74 +#define FRAME_flip04 75 +#define FRAME_flip05 76 +#define FRAME_flip06 77 +#define FRAME_flip07 78 +#define FRAME_flip08 79 +#define FRAME_flip09 80 +#define FRAME_flip10 81 +#define FRAME_flip11 82 +#define FRAME_flip12 83 +#define FRAME_salute01 84 +#define FRAME_salute02 85 +#define FRAME_salute03 86 +#define FRAME_salute04 87 +#define FRAME_salute05 88 +#define FRAME_salute06 89 +#define FRAME_salute07 90 +#define FRAME_salute08 91 +#define FRAME_salute09 92 +#define FRAME_salute10 93 +#define FRAME_salute11 94 +#define FRAME_taunt01 95 +#define FRAME_taunt02 96 +#define FRAME_taunt03 97 +#define FRAME_taunt04 98 +#define FRAME_taunt05 99 +#define FRAME_taunt06 100 +#define FRAME_taunt07 101 +#define FRAME_taunt08 102 +#define FRAME_taunt09 103 +#define FRAME_taunt10 104 +#define FRAME_taunt11 105 +#define FRAME_taunt12 106 +#define FRAME_taunt13 107 +#define FRAME_taunt14 108 +#define FRAME_taunt15 109 +#define FRAME_taunt16 110 +#define FRAME_taunt17 111 +#define FRAME_wave01 112 +#define FRAME_wave02 113 +#define FRAME_wave03 114 +#define FRAME_wave04 115 +#define FRAME_wave05 116 +#define FRAME_wave06 117 +#define FRAME_wave07 118 +#define FRAME_wave08 119 +#define FRAME_wave09 120 +#define FRAME_wave10 121 +#define FRAME_wave11 122 +#define FRAME_point01 123 +#define FRAME_point02 124 +#define FRAME_point03 125 +#define FRAME_point04 126 +#define FRAME_point05 127 +#define FRAME_point06 128 +#define FRAME_point07 129 +#define FRAME_point08 130 +#define FRAME_point09 131 +#define FRAME_point10 132 +#define FRAME_point11 133 +#define FRAME_point12 134 +#define FRAME_crstnd01 135 +#define FRAME_crstnd02 136 +#define FRAME_crstnd03 137 +#define FRAME_crstnd04 138 +#define FRAME_crstnd05 139 +#define FRAME_crstnd06 140 +#define FRAME_crstnd07 141 +#define FRAME_crstnd08 142 +#define FRAME_crstnd09 143 +#define FRAME_crstnd10 144 +#define FRAME_crstnd11 145 +#define FRAME_crstnd12 146 +#define FRAME_crstnd13 147 +#define FRAME_crstnd14 148 +#define FRAME_crstnd15 149 +#define FRAME_crstnd16 150 +#define FRAME_crstnd17 151 +#define FRAME_crstnd18 152 +#define FRAME_crstnd19 153 +#define FRAME_crwalk1 154 +#define FRAME_crwalk2 155 +#define FRAME_crwalk3 156 +#define FRAME_crwalk4 157 +#define FRAME_crwalk5 158 +#define FRAME_crwalk6 159 +#define FRAME_crattak1 160 +#define FRAME_crattak2 161 +#define FRAME_crattak3 162 +#define FRAME_crattak4 163 +#define FRAME_crattak5 164 +#define FRAME_crattak6 165 +#define FRAME_crattak7 166 +#define FRAME_crattak8 167 +#define FRAME_crattak9 168 +#define FRAME_crpain1 169 +#define FRAME_crpain2 170 +#define FRAME_crpain3 171 +#define FRAME_crpain4 172 +#define FRAME_crdeath1 173 +#define FRAME_crdeath2 174 +#define FRAME_crdeath3 175 +#define FRAME_crdeath4 176 +#define FRAME_crdeath5 177 +#define FRAME_death101 178 +#define FRAME_death102 179 +#define FRAME_death103 180 +#define FRAME_death104 181 +#define FRAME_death105 182 +#define FRAME_death106 183 +#define FRAME_death201 184 +#define FRAME_death202 185 +#define FRAME_death203 186 +#define FRAME_death204 187 +#define FRAME_death205 188 +#define FRAME_death206 189 +#define FRAME_death301 190 +#define FRAME_death302 191 +#define FRAME_death303 192 +#define FRAME_death304 193 +#define FRAME_death305 194 +#define FRAME_death306 195 +#define FRAME_death307 196 +#define FRAME_death308 197 + +#define MODEL_SCALE 1.000000 + diff --git a/src/p_client.c b/src/p_client.c new file mode 100644 index 0000000..f0a5c7f --- /dev/null +++ b/src/p_client.c @@ -0,0 +1,2402 @@ +#include "g_local.h" +#include "m_player.h" +#include "bot.h" + +int cumsindex; + +void ClientUserinfoChanged (edict_t *ent, char *userinfo); + +void SP_misc_teleporter_dest (edict_t *ent); + +// +// Gross, ugly, disgustuing hack section +// + +// this function is an ugly as hell hack to fix some map flaws +// +// the coop spawn spots on some maps are SNAFU. There are coop spots +// with the wrong targetname as well as spots with no name at all +// +// we use carnal knowledge of the maps to fix the coop spot targetnames to match +// that of the nearest named single player spot + +static void SP_FixCoopSpots (edict_t *self) +{ + edict_t *spot; + vec3_t d; + + spot = NULL; + + while(1) + { + spot = G_Find(spot, FOFS(classname), "info_player_start"); + if (!spot) + return; + if (!spot->targetname) + continue; + VectorSubtract(self->s.origin, spot->s.origin, d); + if (VectorLength(d) < 384) + { + if ((!self->targetname) || stricmp(self->targetname, spot->targetname) != 0) + { +// gi.dprintf("FixCoopSpots changed %s at %s targetname from %s to %s\n", self->classname, vtos(self->s.origin), self->targetname, spot->targetname); + self->targetname = spot->targetname; + } + return; + } + } +} + +// now if that one wasn't ugly enough for you then try this one on for size +// some maps don't have any coop spots at all, so we need to create them +// where they should have been + +static void SP_CreateCoopSpots (edict_t *self) +{ + edict_t *spot; + + if(stricmp(level.mapname, "security") == 0) + { + spot = G_Spawn(); + spot->classname = "info_player_coop"; + spot->s.origin[0] = 188 - 64; + spot->s.origin[1] = -164; + spot->s.origin[2] = 80; + spot->targetname = "jail3"; + spot->s.angles[1] = 90; + + spot = G_Spawn(); + spot->classname = "info_player_coop"; + spot->s.origin[0] = 188 + 64; + spot->s.origin[1] = -164; + spot->s.origin[2] = 80; + spot->targetname = "jail3"; + spot->s.angles[1] = 90; + + spot = G_Spawn(); + spot->classname = "info_player_coop"; + spot->s.origin[0] = 188 + 128; + spot->s.origin[1] = -164; + spot->s.origin[2] = 80; + spot->targetname = "jail3"; + spot->s.angles[1] = 90; + + return; + } +} + + +/*QUAKED info_player_start (1 0 0) (-16 -16 -24) (16 16 32) +The normal starting point for a level. +*/ +void SP_info_player_start(edict_t *self) +{ + if (!coop->value) + return; + if(stricmp(level.mapname, "security") == 0) + { + // invoke one of our gross, ugly, disgusting hacks + self->think = SP_CreateCoopSpots; + self->nextthink = level.time + FRAMETIME; + } +} + +/*QUAKED info_player_deathmatch (1 0 1) (-16 -16 -24) (16 16 32) +potential spawning position for deathmatch games +*/ +void SP_info_player_deathmatch(edict_t *self) +{ + if (!deathmatch->value) + { + G_FreeEdict (self); + return; + } + SP_misc_teleporter_dest (self); +} + +/*QUAKED info_player_coop (1 0 1) (-16 -16 -24) (16 16 32) +potential spawning position for coop games +*/ + +void SP_info_player_coop(edict_t *self) +{ + if (!coop->value) + { + G_FreeEdict (self); + return; + } + + if((stricmp(level.mapname, "jail2") == 0) || + (stricmp(level.mapname, "jail4") == 0) || + (stricmp(level.mapname, "mine1") == 0) || + (stricmp(level.mapname, "mine2") == 0) || + (stricmp(level.mapname, "mine3") == 0) || + (stricmp(level.mapname, "mine4") == 0) || + (stricmp(level.mapname, "lab") == 0) || + (stricmp(level.mapname, "boss1") == 0) || + (stricmp(level.mapname, "fact3") == 0) || + (stricmp(level.mapname, "biggun") == 0) || + (stricmp(level.mapname, "space") == 0) || + (stricmp(level.mapname, "command") == 0) || + (stricmp(level.mapname, "power2") == 0) || + (stricmp(level.mapname, "strike") == 0)) + { + // invoke one of our gross, ugly, disgusting hacks + self->think = SP_FixCoopSpots; + self->nextthink = level.time + FRAMETIME; + } +} + + +/*QUAKED info_player_intermission (1 0 1) (-16 -16 -24) (16 16 32) +The deathmatch intermission point will be at one of these +Use 'angles' instead of 'angle', so you can set pitch or roll as well as yaw. 'pitch yaw roll' +*/ +void SP_info_player_intermission(void) +{ +} + + +//======================================================================= + + +void player_pain (edict_t *self, edict_t *other, float kick, int damage) +{ + // player pain is handled at the end of the frame in P_DamageFeedback +} + + +qboolean IsFemale (edict_t *ent) +{ + char *info; + + if (!ent->client) + return false; + + info = Info_ValueForKey (ent->client->pers.userinfo, "skin"); + if (info[0] == 'f' || info[0] == 'F') + return true; + return false; +} + + +void ClientObituary (edict_t *self, edict_t *inflictor, edict_t *attacker) +{ + int mod; + char *message; + char *message2; + qboolean ff; + + if (coop->value && attacker->client) + meansOfDeath |= MOD_FRIENDLY_FIRE; + + if (deathmatch->value || coop->value) + { + ff = meansOfDeath & MOD_FRIENDLY_FIRE; + mod = meansOfDeath & ~MOD_FRIENDLY_FIRE; + message = NULL; + message2 = ""; + + switch (mod) + { + case MOD_SUICIDE: + message = "suicides"; + break; + case MOD_FALLING: + message = "cratered"; + break; + case MOD_CRUSH: + message = "was squished"; + break; + case MOD_WATER: + message = "sank like a rock"; + break; + case MOD_SLIME: + message = "melted"; + break; + case MOD_LAVA: + message = "does a back flip into the lava"; + break; + case MOD_EXPLOSIVE: + case MOD_BARREL: + message = "blew up"; + break; + case MOD_EXIT: + message = "found a way out"; + break; + case MOD_TARGET_LASER: + message = "saw the light"; + break; + case MOD_TARGET_BLASTER: + message = "got blasted"; + break; + case MOD_BOMB: + case MOD_SPLASH: + case MOD_TRIGGER_HURT: + message = "was in the wrong place"; + break; + // RAFAEL + case MOD_GEKK: + case MOD_BRAINTENTACLE: + message = "that's gotta hurt"; + break; + } + if (attacker == self) + { + switch (mod) + { + case MOD_HELD_GRENADE: + message = "tried to put the pin back in"; + break; + case MOD_HG_SPLASH: + case MOD_G_SPLASH: + if (IsFemale(self)) + message = "tripped on her own grenade"; + else + message = "tripped on his own grenade"; + break; + case MOD_R_SPLASH: + if (IsFemale(self)) + message = "blew herself up"; + else + message = "blew himself up"; + break; + case MOD_BFG_BLAST: + message = "should have used a smaller gun"; + break; + // RAFAEL 03-MAY-98 + case MOD_TRAP: + message = "sucked into his own trap"; + break; + default: + if (IsFemale(self)) + message = "killed herself"; + else + message = "killed himself"; + break; + } + } + if (message) + { + gi.bprintf (PRINT_MEDIUM, "%s %s.\n", self->client->pers.netname, message); + if (deathmatch->value) + self->client->resp.score--; + self->enemy = NULL; + return; + } + + self->enemy = attacker; + if (attacker && attacker->client) + { + switch (mod) + { + case MOD_BLASTER: + message = "was blasted by"; + break; + case MOD_SHOTGUN: + message = "was gunned down by"; + break; + case MOD_SSHOTGUN: + message = "was blown away by"; + message2 = "'s super shotgun"; + break; + case MOD_MACHINEGUN: + message = "was machinegunned by"; + break; + case MOD_CHAINGUN: + message = "was cut in half by"; + message2 = "'s chaingun"; + break; + case MOD_GRENADE: + message = "was popped by"; + message2 = "'s grenade"; + break; + case MOD_G_SPLASH: + message = "was shredded by"; + message2 = "'s shrapnel"; + break; + case MOD_ROCKET: + message = "ate"; + message2 = "'s rocket"; + break; + case MOD_R_SPLASH: + message = "almost dodged"; + message2 = "'s rocket"; + break; + case MOD_HYPERBLASTER: + message = "was melted by"; + message2 = "'s hyperblaster"; + break; + case MOD_RAILGUN: + message = "was railed by"; + break; + case MOD_BFG_LASER: + message = "saw the pretty lights from"; + message2 = "'s BFG"; + break; + case MOD_BFG_BLAST: + message = "was disintegrated by"; + message2 = "'s BFG blast"; + break; + case MOD_BFG_EFFECT: + message = "couldn't hide from"; + message2 = "'s BFG"; + break; + case MOD_HANDGRENADE: + message = "caught"; + message2 = "'s handgrenade"; + break; + case MOD_HG_SPLASH: + message = "didn't see"; + message2 = "'s handgrenade"; + break; + case MOD_HELD_GRENADE: + message = "feels"; + message2 = "'s pain"; + break; + case MOD_TELEFRAG: + message = "tried to invade"; + message2 = "'s personal space"; + break; +//ZOID + case MOD_GRAPPLE: + message = "was caught by"; + message2 = "'s grapple"; + break; +//ZOID +// RAFAEL 14-APR-98 + case MOD_RIPPER: + message = "ripped to shreds by"; + message2 = "'s ripper gun"; + break; + case MOD_PHALANX: + message = "was evaporated by"; + break; + case MOD_TRAP: + message = "caught in trap by"; + break; +// END 14-APR-98 +//PONKO + case MOD_AIRSTRIKE: + message = "was striked by"; + message2 = "'s airstrike"; + break; +//PONKO + } + if (message) + { + gi.bprintf (PRINT_MEDIUM,"%s %s %s%s\n", self->client->pers.netname, message, attacker->client->pers.netname, message2); + if (deathmatch->value) + { + if (ff) + attacker->client->resp.score--; + else + attacker->client->resp.score++; + } + return; + } + } + } + + gi.bprintf (PRINT_MEDIUM,"%s died.\n", self->client->pers.netname); + if (deathmatch->value) + self->client->resp.score--; +} + + +void Touch_Item (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf); + +void TossClientWeapon (edict_t *self) +{ + gitem_t *item; + edict_t *drop; + qboolean quad; + // RAFAEL + qboolean quadfire; + float dist; + vec3_t v; + edict_t *enemy = NULL; + float spread; + + if(self->enemy && self->enemy != self) + { + if(self->enemy->classname[0] == 'p') + { + + VectorSubtract(self->s.origin,self->enemy->s.origin,v); + dist = VectorLength(v); + if(dist < 200) enemy = self->enemy; + } + } + + if (!deathmatch->value) + return; + + item = self->client->pers.weapon; + if (! self->client->pers.inventory[self->client->ammo_index] ) + item = NULL; + if (item && (strcmp (item->pickup_name, "Blaster") == 0)) + item = NULL; + + if (!((int)(dmflags->value) & DF_QUAD_DROP)) + quad = false; + else + quad = (self->client->quad_framenum > (level.framenum + 10)); + + // RAFAEL + if (!((int)(dmflags->value) & DF_QUADFIRE_DROP)) + quadfire = false; + else + quadfire = (self->client->quadfire_framenum > (level.framenum + 10)); + + + if (item && quad) + spread = 22.5; + else if (item && quadfire) + spread = 12.5; + else + spread = 0.0; + + if (item) + { + self->client->v_angle[YAW] -= spread; + drop = Drop_Item (self, item); + self->client->v_angle[YAW] += spread; + drop->spawnflags = DROPPED_PLAYER_ITEM; + if(enemy) enemy->client->zc.second_target = drop; + } + + if (quad) + { + self->client->v_angle[YAW] += spread; + drop = Drop_Item (self, FindItemByClassname ("item_quad")); + self->client->v_angle[YAW] -= spread; + drop->spawnflags |= DROPPED_PLAYER_ITEM; + + drop->touch = Touch_Item; + drop->nextthink = level.time + (self->client->quad_framenum - level.framenum) * FRAMETIME; + drop->think = G_FreeEdict; + if(enemy) enemy->client->zc.second_target = drop; + } + // RAFAEL + if (quadfire) + { + self->client->v_angle[YAW] += spread; + drop = Drop_Item (self, FindItemByClassname ("item_quadfire")); + self->client->v_angle[YAW] -= spread; + drop->spawnflags |= DROPPED_PLAYER_ITEM; + + drop->touch = Touch_Item; + drop->nextthink = level.time + (self->client->quadfire_framenum - level.framenum) * FRAMETIME; + drop->think = G_FreeEdict; + if(enemy) enemy->client->zc.second_target = drop; + } +} + + +/* +================== +LookAtKiller +================== +*/ +void LookAtKiller (edict_t *self, edict_t *inflictor, edict_t *attacker) +{ + vec3_t dir; + + if (attacker && attacker != world && attacker != self) + { + VectorSubtract (attacker->s.origin, self->s.origin, dir); + } + else if (inflictor && inflictor != world && inflictor != self) + { + VectorSubtract (inflictor->s.origin, self->s.origin, dir); + } + else + { + self->client->killer_yaw = self->s.angles[YAW]; + return; + } + + self->client->killer_yaw = 180/M_PI*atan2(dir[1], dir[0]); +} + +/* +================== +player_die +================== +*/ +void player_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + int n; + + VectorClear (self->avelocity); + + self->takedamage = /*DAMAGE_NO;//*/DAMAGE_YES; + self->movetype = MOVETYPE_TOSS; + + self->s.modelindex2 = 0; // remove linked weapon model +//ZOID + self->s.modelindex3 = 0; // remove linked ctf flag +//ZOID + + self->s.angles[0] = 0; + self->s.angles[2] = 0; + + self->s.sound = 0; + self->client->weapon_sound = 0; + + self->maxs[2] = -8; + +// self->solid = SOLID_NOT; + self->svflags |= SVF_DEADMONSTER; + + if (!self->deadflag) + { + if(self->svflags & SVF_MONSTER) + { + LookAtKiller (self, inflictor, attacker); + self->nextthink = level.time + FRAMETIME; + self->think = Bot_Think; + self->client->respawn_time = level.time + 2.0; + self->s.skinnum = (self - g_edicts - 1); + } + else + { + self->client->respawn_time = level.time + 1.0; + LookAtKiller (self, inflictor, attacker); + } + self->client->ps.pmove.pm_type = PM_DEAD; + ClientObituary (self, inflictor, attacker); +//ZOID + if(ctf->value) CTFFragBonuses(self, inflictor, attacker); +//ZOID + + //旗持ってる場合は落とす + if(self->client->pers.inventory[ITEM_INDEX(zflag_item)]) + zflag_item->drop(self,zflag_item); + + TossClientWeapon (self); +//ZOID + if(ctf->value) + { + CTFPlayerResetGrapple(self); + CTFDeadDropFlag(self); + CTFDeadDropTech(self); + } +//ZOID + if (deathmatch->value && !(self->svflags & SVF_MONSTER)) + Cmd_Help_f (self); // show scores + } + + // remove powerups + self->client->quad_framenum = 0; + self->client->invincible_framenum = 0; + self->client->breather_framenum = 0; + self->client->enviro_framenum = 0; + + // RAFAEL + self->client->quadfire_framenum = 0; + + // clear inventory + memset(self->client->pers.inventory, 0, sizeof(self->client->pers.inventory)); + + if (self->health < -40) + { // gib + gi.sound (self, CHAN_BODY, gi.soundindex ("misc/udeath.wav"), 1, ATTN_NORM, 0); + for (n= 0; n < 4; n++) + ThrowGib (self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC); + ThrowClientHead (self, damage); +//ZOID + self->client->anim_priority = ANIM_DEATH; + self->client->anim_end = 0; +//ZOID. + self->takedamage = DAMAGE_NO; + } + else + { // normal death + if (!self->deadflag) + { + static int i; + + i = (i+1)%3; + // start a death animation + self->client->anim_priority = ANIM_DEATH; + if (self->client->ps.pmove.pm_flags & PMF_DUCKED) + { + self->s.frame = FRAME_crdeath1-1; + self->client->anim_end = FRAME_crdeath5; + } + else switch (i) + { + case 0: + self->s.frame = FRAME_death101-1; + self->client->anim_end = FRAME_death106; + break; + case 1: + self->s.frame = FRAME_death201-1; + self->client->anim_end = FRAME_death206; + break; + case 2: + self->s.frame = FRAME_death301-1; + self->client->anim_end = FRAME_death308; + break; + } + gi.sound (self, CHAN_VOICE, gi.soundindex(va("*death%i.wav", (rand()%4)+1)), 1, ATTN_NORM, 0); + } + } + + self->deadflag = DEAD_DEAD; + + //routing last index move + if(chedit->value && self == &g_edicts[1]) Move_LastRouteIndex(); + + gi.linkentity (self); +} + +//======================================================================= + +/* +============== +InitClientPersistant + +This is only called when the game first initializes in single player, +but is called after each death and level change in deathmatch +============== +*/ +void InitClientPersistant (gclient_t *client) +{ + gitem_t *item; + + memset (&client->pers, 0, sizeof(client->pers)); + +//test +// item = FindItem("Zig Flag"); +// client->pers.selected_item = ITEM_INDEX(item); +// client->pers.inventory[client->pers.selected_item] = 1; +// item = FindItem("Trap"); +// client->pers.inventory[ITEM_INDEX(item)] = 100; +//test + item = /*Fdi_BLASTER;//*/FindItem("Blaster"); + client->pers.selected_item = ITEM_INDEX(item); + client->pers.inventory[client->pers.selected_item] = 1; + + client->pers.weapon = item; +//ZOID + client->pers.lastweapon = item; +//ZOID + +//ZOID + item = FindItem("Grapple"); + if(ctf->value) client->pers.inventory[ITEM_INDEX(item)] = 1; //ponpoko +//ZOID + + client->pers.health = 100; + client->pers.max_health = 100; + + client->pers.max_bullets = 200; + client->pers.max_shells = 100; + client->pers.max_rockets = 50; + client->pers.max_grenades = 50; + client->pers.max_cells = 200; + client->pers.max_slugs = 50; + + // RAFAEL + client->pers.max_magslug = 50; + client->pers.max_trap = 5; + + client->pers.connected = true; +} + + +void InitClientResp (gclient_t *client) +{ +//ZOID + int ctf_team = client->resp.ctf_team; +//ZOID + + memset (&client->resp, 0, sizeof(client->resp)); + +//ZOID + client->resp.ctf_team = ctf_team; +//ZOID + + client->resp.enterframe = level.framenum; + client->resp.coop_respawn = client->pers; + +//ZOID + if (ctf->value && client->resp.ctf_team < CTF_TEAM1) + CTFAssignTeam(client); +//ZOID +} + +/* +================== +SaveClientData + +Some information that should be persistant, like health, +is still stored in the edict structure, so it needs to +be mirrored out to the client structure before all the +edicts are wiped. +================== +*/ +void SaveClientData (void) +{ + int i; + edict_t *ent; + + for (i=0 ; iinuse) + continue; + game.clients[i].pers.health = ent->health; + game.clients[i].pers.max_health = ent->max_health; + game.clients[i].pers.powerArmorActive = (ent->flags & FL_POWER_ARMOR); + if (coop->value) + game.clients[i].pers.score = ent->client->resp.score; + } +} + +void FetchClientEntData (edict_t *ent) +{ + ent->health = ent->client->pers.health; + ent->max_health = ent->client->pers.max_health; + if (ent->client->pers.powerArmorActive) + ent->flags |= FL_POWER_ARMOR; + if (coop->value) + ent->client->resp.score = ent->client->pers.score; +} + + + +/* +======================================================================= + + SelectSpawnPoint + +======================================================================= +*/ + +/* +================ +PlayersRangeFromSpot + +Returns the distance to the nearest player from the given spot +================ +*/ +float PlayersRangeFromSpot (edict_t *spot) +{ + edict_t *player; + float bestplayerdistance; + vec3_t v; + int n; + float playerdistance; + + + bestplayerdistance = 9999999; + + for (n = 1; n <= maxclients->value; n++) + { + player = &g_edicts[n]; + + if (!player->inuse) + continue; + + if (player->health <= 0) + continue; + + VectorSubtract (spot->s.origin, player->s.origin, v); + playerdistance = VectorLength (v); + + if (playerdistance < bestplayerdistance) + bestplayerdistance = playerdistance; + } + + return bestplayerdistance; +} + +/* +================ +SelectRandomDeathmatchSpawnPoint + +go to a random point, but NOT the two points closest +to other players +================ +*/ +edict_t *SelectRandomDeathmatchSpawnPoint (void) +{ + edict_t *spot, *spot1, *spot2; + int count = 0; + int selection; + float range, range1, range2; + + spot = NULL; + range1 = range2 = 99999; + spot1 = spot2 = NULL; + + while ((spot = G_Find (spot, FOFS(classname), "info_player_deathmatch")) != NULL) + { + count++; + range = PlayersRangeFromSpot(spot); + if (range < range1) + { + range1 = range; + spot1 = spot; + } + else if (range < range2) + { + range2 = range; + spot2 = spot; + } + } + + if (!count) + return NULL; + + if (count <= 2) + { + spot1 = spot2 = NULL; + } + else + count -= 2; + + selection = rand() % count; + + spot = NULL; + do + { + spot = G_Find (spot, FOFS(classname), "info_player_deathmatch"); + if (spot == spot1 || spot == spot2) + selection++; + } while(selection--); + + return spot; +} + +/* +================ +SelectFarthestDeathmatchSpawnPoint + +================ +*/ +edict_t *SelectFarthestDeathmatchSpawnPoint (void) +{ + edict_t *bestspot; + float bestdistance, bestplayerdistance; + edict_t *spot; + + + spot = NULL; + bestspot = NULL; + bestdistance = 0; + while ((spot = G_Find (spot, FOFS(classname), "info_player_deathmatch")) != NULL) + { + bestplayerdistance = PlayersRangeFromSpot (spot); + + if (bestplayerdistance > bestdistance) + { + bestspot = spot; + bestdistance = bestplayerdistance; + } + } + + if (bestspot) + { + return bestspot; + } + + // if there is a player just spawned on each and every start spot + // we have no choice to turn one into a telefrag meltdown + spot = G_Find (NULL, FOFS(classname), "info_player_deathmatch"); + + return spot; +} + +edict_t *SelectDeathmatchSpawnPoint (void) +{ + if ( (int)(dmflags->value) & DF_SPAWN_FARTHEST) + return SelectFarthestDeathmatchSpawnPoint (); + else + return SelectRandomDeathmatchSpawnPoint (); +} + + +edict_t *SelectCoopSpawnPoint (edict_t *ent) +{ + int index; + edict_t *spot = NULL; + char *target; + + index = ent->client - game.clients; + + // player 0 starts in normal player spawn point + if (!index) + return NULL; + + spot = NULL; + + // assume there are four coop spots at each spawnpoint + while (1) + { + spot = G_Find (spot, FOFS(classname), "info_player_coop"); + if (!spot) + return NULL; // we didn't have enough... + + target = spot->targetname; + if (!target) + target = ""; + if ( Q_stricmp(game.spawnpoint, target) == 0 ) + { // this is a coop spawn point for one of the clients here + index--; + if (!index) + return spot; // this is it + } + } + + + return spot; +} + + +/* +=========== +SelectSpawnPoint + +Chooses a player start, deathmatch start, coop start, etc +============ +*/ +void SelectSpawnPoint (edict_t *ent, vec3_t origin, vec3_t angles) +{ + edict_t *spot = NULL; + + if (deathmatch->value) +//ZOID + if (ctf->value) + spot = SelectCTFSpawnPoint(ent); + else +//ZOID + spot = SelectDeathmatchSpawnPoint (); + else if (coop->value) + spot = SelectCoopSpawnPoint (ent); + + // find a single player start spot + if (!spot) + { + while ((spot = G_Find (spot, FOFS(classname), "info_player_start")) != NULL) + { + if (!game.spawnpoint[0] && !spot->targetname) + break; + + if (!game.spawnpoint[0] || !spot->targetname) + continue; + + if (Q_stricmp(game.spawnpoint, spot->targetname) == 0) + break; + } + + if (!spot) + { + if (!game.spawnpoint[0]) + { // there wasn't a spawnpoint without a target, so use any + spot = G_Find (spot, FOFS(classname), "info_player_start"); + } + if (!spot) + gi.error ("Couldn't find spawn point %s\n", game.spawnpoint); + } + } + + VectorCopy (spot->s.origin, origin); + if(ent->svflags & SVF_MONSTER) origin[2] += 32; + else origin[2] += 9; + VectorCopy (spot->s.angles, angles); +} + +//====================================================================== + + +void InitBodyQue (void) +{ + int i; + edict_t *ent; + + level.body_que = 0; + for (i=0; iclassname = "bodyque"; + } +} + + +void body_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + int n; + + if (self->health < -40) + { + gi.sound (self, CHAN_BODY, gi.soundindex ("misc/udeath.wav"), 1, ATTN_NORM, 0); + for (n= 0; n < 4; n++) + ThrowGib (self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC); + self->s.origin[2] -= 48; + ThrowClientHead (self, damage); + self->s.frame = 0; + self->takedamage = DAMAGE_YES;//DAMAGE_NO; + self->solid = SOLID_NOT; + } +} + +void CopyToBodyQue (edict_t *ent) +{ + edict_t *body; + + // grab a body que and cycle to the next one + body = &g_edicts[(int)maxclients->value + level.body_que + 1]; + level.body_que = (level.body_que + 1) % BODY_QUEUE_SIZE; + + // FIXME: send an effect on the removed body + + gi.unlinkentity (ent); + + gi.unlinkentity (body); + body->s = ent->s; + body->s.number = body - g_edicts; + + //強引にフレームセット + if(body->s.modelindex == skullindex || body->s.modelindex == headindex) body->s.frame = 0; + + body->svflags = ent->svflags; + VectorCopy (ent->mins, body->mins); + VectorCopy (ent->maxs, body->maxs); + VectorCopy (ent->absmin, body->absmin); + VectorCopy (ent->absmax, body->absmax); + VectorCopy (ent->size, body->size); +// body->solid = SOLID_NOT;//ent->solid; + body->clipmask = 0;//ent->clipmask; + body->owner = ent->owner; + body->movetype = MOVETYPE_TOSS;//ent->movetype; + + body->die = body_die; + if(ent->health < -40) + { + body->takedamage = DAMAGE_NO;//DAMAGE_YES; + body->solid = SOLID_NOT; + } + else + { + body->solid = ent->solid; + body->takedamage = DAMAGE_YES; + } + + gi.linkentity (body); +} + + +void respawn (edict_t *self) +{ + if (deathmatch->value || coop->value) + { + // spectator's don't leave bodies + if (self->movetype != MOVETYPE_NOCLIP) + CopyToBodyQue (self); + self->svflags &= ~SVF_NOCLIENT; + PutClientInServer (self); + + // add a teleportation effect + self->s.event = EV_PLAYER_TELEPORT; + + // hold in place briefly + self->client->ps.pmove.pm_flags = PMF_TIME_TELEPORT; + self->client->ps.pmove.pm_time = 14; + + self->client->respawn_time = level.time; + return; + } + + // restart the entire server + gi.AddCommandString ("menu_loadgame\n"); +} + +/* + * only called when pers.spectator changes + * note that resp.spectator should be the opposite of pers.spectator here + */ +void spectator_respawn (edict_t *ent) +{ + int i, numspec; + + // if the user wants to become a spectator, make sure he doesn't + // exceed max_spectators + + if (ent->client->pers.spectator) { + char *value = Info_ValueForKey (ent->client->pers.userinfo, "spectator"); + if (*spectator_password->string && + strcmp(spectator_password->string, "none") && + strcmp(spectator_password->string, value)) { + gi.cprintf(ent, PRINT_HIGH, "Spectator password incorrect.\n"); + ent->client->pers.spectator = false; + gi.WriteByte (svc_stufftext); + gi.WriteString ("spectator 0\n"); + gi.unicast(ent, true); + return; + } + + // count spectators + for (i = 1, numspec = 0; i <= maxclients->value; i++) + if (g_edicts[i].inuse && g_edicts[i].client->pers.spectator) + numspec++; + + if (numspec >= maxspectators->value) { + gi.cprintf(ent, PRINT_HIGH, "Server spectator limit is full."); + ent->client->pers.spectator = false; + // reset his spectator var + gi.WriteByte (svc_stufftext); + gi.WriteString ("spectator 0\n"); + gi.unicast(ent, true); + return; + } + } else { + // he was a spectator and wants to join the game + // he must have the right password + char *value = Info_ValueForKey (ent->client->pers.userinfo, "password"); + if (*password->string && strcmp(password->string, "none") && + strcmp(password->string, value)) { + gi.cprintf(ent, PRINT_HIGH, "Password incorrect.\n"); + ent->client->pers.spectator = true; + gi.WriteByte (svc_stufftext); + gi.WriteString ("spectator 1\n"); + gi.unicast(ent, true); + return; + } + } + + // clear client on respawn + ent->client->resp.score = ent->client->pers.score = 0; + + ent->svflags &= ~SVF_NOCLIENT; + PutClientInServer (ent); + + // add a teleportation effect + if (!ent->client->pers.spectator) { + // send effect + gi.WriteByte (svc_muzzleflash); + gi.WriteShort (ent-g_edicts); + gi.WriteByte (MZ_LOGIN); + gi.multicast (ent->s.origin, MULTICAST_PVS); + + // hold in place briefly + ent->client->ps.pmove.pm_flags = PMF_TIME_TELEPORT; + ent->client->ps.pmove.pm_time = 14; + } + + ent->client->respawn_time = level.time; + + if (ent->client->pers.spectator) + gi.bprintf (PRINT_HIGH, "%s has moved to the sidelines\n", ent->client->pers.netname); + else + gi.bprintf (PRINT_HIGH, "%s joined the game\n", ent->client->pers.netname); +} + +//============================================================== + + +/* +=========== +PutClientInServer + +Called when a player connects to a server or respawns in +a deathmatch. +============ +*/ +// ### Hentai ### BEGIN +void ShowGun(edict_t *ent); +// ### Hentai ### END + +void PutClientInServer (edict_t *ent) +{ + vec3_t mins = {-16, -16, -24}; + vec3_t maxs = {16, 16, 32}; + int index; + vec3_t spawn_origin, spawn_angles; + gclient_t *client; + int i; + client_persistant_t saved; + client_respawn_t resp; + + zgcl_t zgcl; + + // find a spawn point + // do it before setting health back up, so farthest + // ranging doesn't count this client + SelectSpawnPoint (ent, spawn_origin, spawn_angles); + + index = ent-g_edicts-1; + client = ent->client; + + // deathmatch wipes most client data every spawn + if (deathmatch->value) + { + char userinfo[MAX_INFO_STRING]; + + resp = client->resp; + memcpy (userinfo, client->pers.userinfo, sizeof(userinfo)); + InitClientPersistant (client); + ClientUserinfoChanged (ent, userinfo); + } + else if (coop->value) + { + int n; + char userinfo[MAX_INFO_STRING]; + + resp = client->resp; + memcpy (userinfo, client->pers.userinfo, sizeof(userinfo)); + // this is kind of ugly, but it's how we want to handle keys in coop + for (n = 0; n < MAX_ITEMS; n++) + { + if (itemlist[n].flags & IT_KEY) + resp.coop_respawn.inventory[n] = client->pers.inventory[n]; + } + client->pers = resp.coop_respawn; + ClientUserinfoChanged (ent, userinfo); + if (resp.score > client->pers.score) + client->pers.score = resp.score; + } + else + { + memset (&resp, 0, sizeof(resp)); + } + + // clear everything but the persistant data + saved = client->pers; + memcpy (&zgcl,&client->zc,sizeof(zgcl_t)); + memset (client, 0, sizeof(*client)); + memcpy (&client->zc,&zgcl,sizeof(zgcl_t)); + client->pers = saved; + if (client->pers.health <= 0) + InitClientPersistant(client); + client->resp = resp; + + // copy some data from the client to the entity + FetchClientEntData (ent); + + // clear entity values + ent->groundentity = NULL; + ent->client = &game.clients[index]; + ent->takedamage = DAMAGE_AIM; + ent->movetype = MOVETYPE_WALK; + ent->viewheight = 22; + ent->inuse = true; + ent->classname = "player"; + ent->mass = 200; + ent->solid = SOLID_BBOX; + ent->deadflag = DEAD_NO; + ent->air_finished = level.time + 12; + ent->clipmask = MASK_PLAYERSOLID; + ent->model = "players/male/tris.md2"; + ent->pain = player_pain; + ent->die = player_die; + ent->waterlevel = 0; + ent->watertype = 0; + ent->flags &= ~FL_NO_KNOCKBACK; + ent->svflags &= ~SVF_DEADMONSTER; + ent->svflags &= ~SVF_MONSTER; +//ponko + ent->client->zc.aiming = 0; + ent->client->zc.distance = 90; +//ponko + VectorCopy (mins, ent->mins); + VectorCopy (maxs, ent->maxs); + VectorClear (ent->velocity); + + // clear playerstate values + memset (&ent->client->ps, 0, sizeof(client->ps)); + + client->ps.pmove.origin[0] = spawn_origin[0]*8; + client->ps.pmove.origin[1] = spawn_origin[1]*8; + client->ps.pmove.origin[2] = spawn_origin[2]*8; +//ZOID + client->ps.pmove.pm_flags &= ~PMF_NO_PREDICTION; +//ZOID + + if (deathmatch->value && ((int)dmflags->value & DF_FIXED_FOV)) + { + client->ps.fov = 90; + } + else + { + client->ps.fov = atoi(Info_ValueForKey(client->pers.userinfo, "fov")); + if (client->ps.fov < 1) + client->ps.fov = 90; + else if (client->ps.fov > 160) + client->ps.fov = 160; + } + + client->ps.gunindex = gi.modelindex(client->pers.weapon->view_model); + + // clear entity state values + ent->s.effects = 0; + ent->s.skinnum = ent - g_edicts - 1; + ent->s.modelindex = 255; // will use the skin specified model +// ent->s.modelindex2 = 255; // custom gun model + ShowGun(ent); // ### Hentai ### special gun model + ent->s.frame = 0; + VectorCopy (spawn_origin, ent->s.origin); + ent->s.origin[2] += 1; // make sure off ground + VectorCopy (ent->s.origin, ent->s.old_origin); + + // set the delta angle + for (i=0 ; i<3 ; i++) + client->ps.pmove.delta_angles[i] = ANGLE2SHORT(spawn_angles[i] - client->resp.cmd_angles[i]); + + ent->s.angles[PITCH] = 0; + ent->s.angles[YAW] = spawn_angles[YAW]; + ent->s.angles[ROLL] = 0; + VectorCopy (ent->s.angles, client->ps.viewangles); + VectorCopy (ent->s.angles, client->v_angle); + + // spawn a spectator + if (client->pers.spectator) { + client->chase_target = NULL; + + client->resp.spectator = true; + + ent->movetype = MOVETYPE_NOCLIP; + ent->solid = SOLID_NOT; + ent->svflags |= SVF_NOCLIENT; + ent->client->ps.gunindex = 0; + gi.linkentity (ent); + return; + } else + client->resp.spectator = false; + +//ZOID + if(ctf->value) + { + if (CTFStartClient(ent)) + return; + } +//ZOID +//ponpoko +/* if(hokuto->value) + { + if(ZigockStartClient(ent)) + return; + }*/ +//ponpoko + + if (!KillBox (ent)) + { // could't spawn in? + } + + gi.linkentity (ent); + + // force the current weapon up + client->newweapon = client->pers.weapon; + ChangeWeapon (ent); +} + +/* +===================== +ClientBeginDeathmatch + +A client has just connected to the server in +deathmatch mode, so clear everything out before starting them. +===================== +*/ + +void ClientBeginDeathmatch (edict_t *ent) +{ + G_InitEdict (ent); + + InitClientResp (ent->client); + + // locate ent at a spawn point + PutClientInServer (ent); + + // zgcl clear +// memset (&ent->client->zc,0,sizeof(zgcl_t)); + + // send effect + gi.WriteByte (svc_muzzleflash); + gi.WriteShort (ent-g_edicts); + gi.WriteByte (MZ_LOGIN); + gi.multicast (ent->s.origin, MULTICAST_PVS); + + gi.bprintf (PRINT_HIGH, "%s entered the game\n", ent->client->pers.netname); + + + gi.centerprintf(ent,ClientMessage); + + // make sure all view stuff is valid + ClientEndServerFrame (ent); +} + + +/* +=========== +ClientBegin + +called when a client has finished connecting, and is ready +to be placed into the game. This will happen every level load. +============ +*/ +void ClientBegin (edict_t *ent) +{ + int i; + + ent->client = game.clients + (ent - g_edicts - 1); + + if (deathmatch->value) + { + ClientBeginDeathmatch (ent); + return; + } + + // if there is already a body waiting for us (a loadgame), just + // take it, otherwise spawn one from scratch + if (ent->inuse == true) + { + // the client has cleared the client side viewangles upon + // connecting to the server, which is different than the + // state when the game is saved, so we need to compensate + // with deltaangles + for (i=0 ; i<3 ; i++) + ent->client->ps.pmove.delta_angles[i] = ANGLE2SHORT(ent->client->ps.viewangles[i]); + } + else + { + // a spawn point will completely reinitialize the entity + // except for the persistant data that was initialized at + // ClientConnect() time + G_InitEdict (ent); + ent->classname = "player"; + InitClientResp (ent->client); + PutClientInServer (ent); + } + + if (level.intermissiontime) + { + MoveClientToIntermission (ent); + } + else + { + // send effect if in a multiplayer game + if (game.maxclients > 1) + { + gi.WriteByte (svc_muzzleflash); + gi.WriteShort (ent-g_edicts); + gi.WriteByte (MZ_LOGIN); + gi.multicast (ent->s.origin, MULTICAST_PVS); + + gi.bprintf (PRINT_HIGH, "%s entered the game\n", ent->client->pers.netname); + } + } + + // make sure all view stuff is valid + ClientEndServerFrame (ent); +} + +/* +=========== +ClientUserInfoChanged + +called whenever the player updates a userinfo variable. + +The game can override any of the settings in place +(forcing skins or names, etc) before copying it off. +============ +*/ +void ClientUserinfoChanged (edict_t *ent, char *userinfo) +{ + char *s; + int playernum; + + // check for malformed or illegal info strings + if (!Info_Validate(userinfo)) + { + strcpy (userinfo, "\\name\\badinfo\\skin\\male/grunt"); + } + + // set name + s = Info_ValueForKey (userinfo, "name"); + strncpy (ent->client->pers.netname, s, sizeof(ent->client->pers.netname)-1); + + // set spectator + s = Info_ValueForKey (userinfo, "spectator"); + // spectators are only supported in deathmatch + if (deathmatch->value && !ctf->value && *s && strcmp(s, "0")) + ent->client->pers.spectator = true; + else + ent->client->pers.spectator = false; + + // set skin + s = Info_ValueForKey (userinfo, "skin"); + + playernum = ent-g_edicts-1; + + // combine name and skin into a configstring +//ZOID + if (ctf->value) + CTFAssignSkin(ent, s); + else +//ZOID + gi.configstring (CS_PLAYERSKINS+playernum, va("%s\\%s", ent->client->pers.netname, s) ); + + // fov + if (deathmatch->value && ((int)dmflags->value & DF_FIXED_FOV)) + { + ent->client->ps.fov = 90; + } + else + { + ent->client->ps.fov = atoi(Info_ValueForKey(userinfo, "fov")); + if (ent->client->ps.fov < 1) + ent->client->ps.fov = 90; + else if (ent->client->ps.fov > 160) + ent->client->ps.fov = 160; + } + + // handedness + s = Info_ValueForKey (userinfo, "hand"); + if (strlen(s)) + { + ent->client->pers.hand = atoi(s); + } + + // save off the userinfo in case we want to check something later + strncpy (ent->client->pers.userinfo, userinfo, sizeof(ent->client->pers.userinfo)-1); +} + + +/* +=========== +ClientConnect + +Called when a player begins connecting to the server. +The game can refuse entrance to a client by returning false. +If the client is allowed, the connection process will continue +and eventually get to ClientBegin() +Changing levels will NOT cause this to be called again, but +loadgames will. +============ +*/ +qboolean ClientConnect (edict_t *ent, char *userinfo) +{ + char *value; + + // check to see if they are on the banned IP list +/* value = Info_ValueForKey (userinfo, "ip"); + + // check for a password + value = Info_ValueForKey (userinfo, "password"); + if (strcmp(password->string, value) != 0) + return false;*/ + + // check to see if they are on the banned IP list + value = Info_ValueForKey (userinfo, "ip"); + if (SV_FilterPacket(value)) { + Info_SetValueForKey(userinfo, "rejmsg", "Banned."); + return false; + } + + // check for a spectator + value = Info_ValueForKey (userinfo, "spectator"); + if (deathmatch->value && *value && !ctf->value && strcmp(value, "0")) { + int i, numspec; + + if (*spectator_password->string && + strcmp(spectator_password->string, "none") && + strcmp(spectator_password->string, value)) { + Info_SetValueForKey(userinfo, "rejmsg", "Spectator password required or incorrect."); + return false; + } + + // count spectators + for (i = numspec = 0; i < maxclients->value; i++) + if (g_edicts[i+1].inuse && g_edicts[i+1].client->pers.spectator) + numspec++; + + if (numspec >= maxspectators->value) { + Info_SetValueForKey(userinfo, "rejmsg", "Server spectator limit is full."); + return false; + } + } else { + // check for a password + value = Info_ValueForKey (userinfo, "password"); + if (*password->string && strcmp(password->string, "none") && + strcmp(password->string, value)) { + Info_SetValueForKey(userinfo, "rejmsg", "Password required or incorrect."); + return false; + } + } + + // they can connect + ent->client = game.clients + (ent - g_edicts - 1); + + // if there is already a body waiting for us (a loadgame), just + // take it, otherwise spawn one from scratch + if (ent->inuse == false) + { + // clear the respawning variables +//ZOID -- force team join + ent->client->resp.ctf_team = -1; +//ZOID + InitClientResp (ent->client); + if (!game.autosaved || !ent->client->pers.weapon) + InitClientPersistant (ent->client); + } + + ClientUserinfoChanged (ent, userinfo); + + if (game.maxclients > 1) + gi.dprintf ("%s connected\n", ent->client->pers.netname); + + ent->client->pers.connected = true; + return true; +} + +/* +=========== +ClientDisconnect + +Called when a player drops from the server. +Will not be called between levels. +============ +*/ +void ClientDisconnect (edict_t *ent) +{ + int playernum; + + if (!ent->client) + return; + + gi.bprintf (PRINT_HIGH, "%s disconnected\n", ent->client->pers.netname); +//ZOID + CTFDeadDropFlag(ent); + CTFDeadDropTech(ent); +//ZOID + + // send effect + gi.WriteByte (svc_muzzleflash); + gi.WriteShort (ent-g_edicts); + gi.WriteByte (MZ_LOGOUT); + gi.multicast (ent->s.origin, MULTICAST_PVS); + + gi.unlinkentity (ent); + ent->s.modelindex = 0; + ent->solid = SOLID_NOT; + ent->inuse = false; + ent->classname = "disconnected"; + ent->client->pers.connected = false; + + playernum = ent-g_edicts-1; + gi.configstring (CS_PLAYERSKINS+playernum, ""); +} + + +//============================================================== + + +edict_t *pm_passent; + +// pmove doesn't need to know about passent and contentmask +trace_t PM_trace (vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end) +{ + if (pm_passent->health > 0) + return gi.trace (start, mins, maxs, end, pm_passent, MASK_PLAYERSOLID); + else + return gi.trace (start, mins, maxs, end, pm_passent, MASK_DEADSOLID); +} + +unsigned CheckBlock (void *b, int c) +{ + int v,i; + v = 0; + for (i=0 ; is, sizeof(pm->s)); + c2 = CheckBlock (&pm->cmd, sizeof(pm->cmd)); + Com_Printf ("sv %3i:%i %i\n", pm->cmd.impulse, c1, c2); +} + +/* +============== +ClientThink + +This will be called once for each client frame, which will +usually be a couple times for each server frame. +============== +*/ + +//edict_t *GetBotFlag1(); +//edict_t *GetBotFlag2(); +void SpawnExtra(vec3_t position,char *classname); + +void Get_Position ( edict_t *ent, vec3_t position ) +{ + float yaw,pitch; + + yaw = ent->s.angles[YAW]; + pitch = ent->s.angles[PITCH]; + + yaw = yaw * M_PI * 2 / 360; + pitch = pitch * M_PI * 2 / 360; + + position[0] = cos(yaw) * cos(pitch); + position[1] = sin(yaw) * cos(pitch); + position[2] = -sin(pitch); +} + +void ChainPodThink (edict_t *ent) +{ + if(ent->owner == NULL )return; + + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_BFG_LASER); + gi.WritePosition (ent->s.origin); + gi.WritePosition (ent->owner->s.origin); + gi.multicast (ent->s.origin, MULTICAST_PHS); + + if(ent->target_ent != NULL) + { + if(Q_stricmp (ent->target_ent->classname, "item_flag_team2") == 0) + { + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_BFG_LASER); + gi.WritePosition (ent->s.origin); + gi.WritePosition (ent->target_ent->s.origin); + gi.multicast (ent->s.origin, MULTICAST_PHS); + } + } + ent->nextthink = level.time + FRAMETIME * 10; +} +qboolean Bot_traceX (edict_t *ent,edict_t *other); +qboolean ChkTFlg(); + + +qboolean TraceX (edict_t *ent,vec3_t p2) +{ + trace_t rs_trace; + vec3_t v1,v2; + + int contents; + + + contents = CONTENTS_SOLID | CONTENTS_WINDOW; + + if(!(ent->svflags & SVF_MONSTER)) + { + if(ent->client->zc.waterstate) + { + VectorCopy(ent->mins,v1); + VectorCopy(ent->maxs,v2); + +/* v1[0] -= 4; + v1[1] -= 4; + v2[0] += 4; + v2[1] += 4;*/ + } + else if(!(ent->client->ps.pmove.pm_flags & PMF_DUCKED)) + { + VectorSet(v1,-16,-16,-4); + VectorSet(v2,16,16,32); + } + else + { +// VectorCopy(ent->mins,v1); +// VectorCopy(ent->maxs,v2); + VectorSet(v1,-4,-4,-4); + VectorSet(v2,4,4,4); + } + } + else + { + VectorSet(v1,0,0,0); + VectorSet(v2,0,0,0); + contents |= CONTENTS_LAVA | CONTENTS_SLIME; + } + + rs_trace = gi.trace (ent->s.origin, v1, v2, p2 ,ent, contents ); + if(rs_trace.fraction == 1.0 && !rs_trace.allsolid && !rs_trace.startsolid ) return true; + + if(ent->client->zc.route_trace && rs_trace.ent && (ent->svflags & SVF_MONSTER)) + { + //if(!rs_trace.ent->targetname) + if(!Q_stricmp(rs_trace.ent->classname, "func_door")) + { + if(rs_trace.ent->moveinfo.state == PSTATE_UP) return true; + else return false; + } +// if(!Q_stricmp(rs_trace.ent->classname, "func_train")) return true; + } + + return false; +} + +void ClientThink (edict_t *ent, usercmd_t *ucmd) +{ + gclient_t *client; + edict_t *other; + int i, j, k,l,oldwaterstate; + byte impulse; + pmove_t pm; + + vec3_t min,max,v,vv; + float x; + trace_t rs_trace; + + static edict_t *old_ground; + static qboolean wasground; + + impulse = ucmd->impulse; + + + if(impulse == 1) gi.bprintf(PRINT_HIGH,"%f\n",ent->s.origin[2]); + + //-------------------------------------------------------------------------------------- + //Target check + if(ent->client->zc.first_target) + { + if(!ent->client->zc.first_target->inuse) ent->client->zc.first_target = NULL; + else if(!ent->client->zc.first_target->deadflag) ent->client->zc.first_target = NULL; + } + + //-------------------------------------------------------------------------------------- + //get JumpMax + if(JumpMax == 0) + { + x = VEL_BOT_JUMP - ent->gravity * sv_gravity->value * FRAMETIME; + JumpMax = 0; + while(1) + { + JumpMax += x * FRAMETIME; + x -= ent->gravity * sv_gravity->value * FRAMETIME; + if( x < 0 ) break; + } + } + + //route nodeput + j = 0; + if(ent->client->ctf_grapple && ent->client->ctf_grapplestate > CTF_GRAPPLE_STATE_FLY) j = 1; + + if(!j && chedit->value && CurrentIndex < MAXNODES && !ent->deadflag && ent == &g_edicts[1]) + { + if(targetindex > 0) + { + if(ent->target_ent == NULL) return; + other = ent->target_ent; + + if(!TraceX(ent,other->s.origin)) + { + k = 0; + i = other->client->zc.routeindex; + while(1) + { + if(i + 1 >= CurrentIndex) + { + j = Route[i + 1].state; + if(j == GRS_ONTRAIN) if(Route[i + 1].ent->trainteam) break; + Get_RouteOrigin(i + 1,v); + if(!TraceX(ent,other->s.origin)) + { + break; + } + } + else break; + i++; + } + Get_RouteOrigin(i + 1,v); + VectorCopy(v,ent->s.origin); + } + + VectorSubtract(other->s.origin,ent->s.origin,vv); + ent->client->ps.viewangles[YAW] = Get_yaw(vv); + ent->client->v_angle[YAW] = ent->client->ps.viewangles[YAW]; + + ent->client->ps.viewangles[PITCH] = Get_pitch(vv); + ent->client->v_angle[PITCH] = ent->client->ps.viewangles[PITCH]; + ent->viewheight = 22; + + ent->client->ps.pmove.pm_flags |= PMF_NO_PREDICTION ; + ent->client->ps.pmove.pm_flags = PM_FREEZE; + gi.linkentity(ent); + + return; + } + + oldwaterstate = ent->client->zc.waterstate; + Get_WaterState(ent); + i = false; + l = GRS_NORMAL; + if(CurrentIndex > 0) Get_RouteOrigin(CurrentIndex - 1,v); + if(!Route[CurrentIndex].index) + { + VectorCopy(ent->s.origin,v); + old_ground = ent->groundentity; +//gi.bprintf(PRINT_HIGH,"1\n"); + if(ent->groundentity) + i = true; + } + else if(!TraceX(ent,v) /*&& ent->groundentity*/) + { + VectorCopy(ent->s.old_origin,v); +//gi.bprintf(PRINT_HIGH,"2\n"); + i = 3; + if(0/*ent->groundentity*/) + { + if(ent->groundentity->classname[0] == 'f') i = false; + } + } + else if(ent->client->zc.waterstate != oldwaterstate) + { + i = true; + if(ent->groundentity ) + { + if(!Q_stricmp(ent->groundentity->classname, "func_train") + || !Q_stricmp(ent->groundentity->classname, "func_plat") + || !Q_stricmp(ent->groundentity->classname, "func_door")) i = false; + } + + if(ent->client->zc.waterstate > oldwaterstate) VectorCopy(ent->s.origin,v); + else VectorCopy(ent->s.old_origin,v); +//gi.bprintf(PRINT_HIGH,"5\n"); + } + else if(fabs(v[2] - ent->s.origin[2]) > 20) + { + if(ent->groundentity && ent->waterlevel < 2) + { + k = true; + if(k) + { + VectorCopy(ent->s.origin,v); +//gi.bprintf(PRINT_HIGH,"3\n"); + i = true; + } + } + + } + else if(((/*ent->velocity[2] > 10 &&*/ !ent->groundentity && wasground == true) + || (/*ent->velocity[2] < -0.5 &&*/ ent->groundentity && wasground == false)) + && Route[CurrentIndex - 1].state <= GRS_ITEMS) + { + j = false; + k = true;//false; + VectorCopy(ent->s.old_origin,v); + v[2] -= 2; + rs_trace = gi.trace(ent->s.old_origin,ent->mins ,ent->maxs, v ,ent,MASK_PLAYERSOLID); + if(rs_trace.fraction != 1.0) j = true; + + if(old_ground) + { + if(!Q_stricmp(old_ground->classname, "func_train") + || !Q_stricmp(old_ground->classname, "func_plat") + || !Q_stricmp(old_ground->classname, "func_door")) k = false; + } + if(!ent->groundentity /*&& j*/&& wasground == true && k) + { + VectorCopy(ent->s.old_origin,v); +//gi.bprintf(PRINT_HIGH,"6\n"); + i = true; + } + else if(ent->groundentity /*&& !j*/&& wasground == false && k) + { +// VectorSubtract(ent->s.origin) + + VectorCopy(ent->s.origin,v); +//gi.bprintf(PRINT_HIGH,"7\n"); + i = true; + } + + } + else if(Route[CurrentIndex-1].index > 1) + { + k = true; + if(0/*old_ground*/) + { + if(!Q_stricmp(old_ground->classname, "func_train") + || !Q_stricmp(old_ground->classname, "func_plat") + || !Q_stricmp(old_ground->classname, "func_door")) k = false; + } + Get_RouteOrigin(CurrentIndex - 1,min); + Get_RouteOrigin(CurrentIndex - 2,max); + VectorSubtract(min,max,v); + x = Get_yaw(v); + VectorSubtract(ent->s.origin,/*Route[CurrentIndex-1].Pt*/ent->s.old_origin,v); + if(VectorLength(v) > 0 && Get_vec_yaw(v,x) > 45 && k ) + { + VectorCopy(ent->s.old_origin,v); +//gi.bprintf(PRINT_HIGH,"8\n"); + i = true; + } + } + + if(ent->groundentity) + { + if(ent->groundentity != old_ground) + { + other = old_ground; + old_ground = ent->groundentity; + if(!Q_stricmp(old_ground->classname, "func_plat")) + { + if(old_ground->union_ent) + { + if(old_ground->union_ent->inuse && old_ground->union_ent->classname[0] == 'R') + { +//gi.bprintf(PRINT_HIGH,"plat put\n"); + VectorCopy(old_ground->monsterinfo.last_sighting,v); + l = GRS_ONPLAT; + i = 2; + } + } + } + else if(!Q_stricmp(old_ground->classname, "func_train")) + { + if(old_ground->union_ent) + { + if(old_ground->union_ent->inuse && old_ground->union_ent->classname[0] == 'R') + { + VectorCopy(old_ground->monsterinfo.last_sighting,v); + l = GRS_ONTRAIN; + i = 2; + } + } + } + else if(!Q_stricmp(old_ground->classname, "func_door")) + { + k = false; + if(old_ground->targetname && old_ground->union_ent) + { + if(TraceX(ent,old_ground->union_ent->s.origin) + && fabs(ent->s.origin[2] - old_ground->union_ent->s.origin[2]) < JumpMax) + { + VectorCopy(old_ground->monsterinfo.last_sighting,v); + l = GRS_ONDOOR; + i = 2; + } + else k = true; + } + else k = true; + if(k && i) + { + i = 2; + old_ground = other; + } + } + } + } + if(old_ground) + { + if(old_ground->classname[0] == 'f' && i != 2) + { + if(!Q_stricmp(old_ground->classname, "func_train") + || !Q_stricmp(old_ground->classname, "func_plat") + || !Q_stricmp(old_ground->classname, "func_door")) i = false; + } + } + + if(Route[CurrentIndex-1].index > 0 && i == true) + { + Get_RouteOrigin(CurrentIndex - 1,max); + VectorSubtract(max,v,vv); + if(VectorLength(vv) <= 32 ) i = false; + } + + if(l == GRS_ONTRAIN || l == GRS_ONPLAT || l == GRS_ONDOOR) + { + if(Route[CurrentIndex - 1].ent == old_ground) i = false; + } + + if(i) + { + if(l == GRS_NORMAL && ent->groundentity) + { + if(!Q_stricmp(old_ground->classname, "func_rotating")) + { + l = GRS_ONROTATE; +// gi.bprintf(PRINT_HIGH,"On Rotate\n"); + } + } + + VectorCopy(v,Route[CurrentIndex].Pt); + Route[CurrentIndex].state = l; + if(l > GRS_ITEMS && l <= GRS_ONTRAIN) Route[CurrentIndex].ent = old_ground; + else if(l == GRS_ONDOOR) Route[CurrentIndex].ent = old_ground; + + if(l == GRS_ONTRAIN && old_ground->trainteam && old_ground->target_ent) + { + if(!Q_stricmp(old_ground->target_ent->classname,"path_corner")) + VectorCopy(old_ground->target_ent->s.origin,Route[CurrentIndex].Tcourner); + +//gi.bprintf(PRINT_HIGH,"get chain\n"); + } + //when normal or items + if(++CurrentIndex < MAXNODES) + { + gi.bprintf(PRINT_HIGH,"Last %i pod(s).\n",MAXNODES - CurrentIndex); + memset(&Route[CurrentIndex],0,sizeof(route_t)); //initialize + Route[CurrentIndex].index = Route[CurrentIndex - 1].index +1; + } + } +// VectorCopy(ent->s.origin,old_origin); + if(ent->groundentity != NULL) wasground = true; + else wasground = false; + } + + + +//-------------------------------------- + level.current_entity = ent; + client = ent->client; + + if (level.intermissiontime) + { + client->ps.pmove.pm_type = PM_FREEZE; + // can exit intermission after five seconds + if (level.time > level.intermissiontime + 5.0 + && (ucmd->buttons & BUTTON_ANY) ) + level.exitintermission = true; + return; + } + + pm_passent = ent; + + if (ent->client->chase_target) { + + client->resp.cmd_angles[0] = SHORT2ANGLE(ucmd->angles[0]); + client->resp.cmd_angles[1] = SHORT2ANGLE(ucmd->angles[1]); + client->resp.cmd_angles[2] = SHORT2ANGLE(ucmd->angles[2]); + + } else { + + // set up for pmove + memset (&pm, 0, sizeof(pm)); + + if (ent->movetype == MOVETYPE_NOCLIP) + client->ps.pmove.pm_type = PM_SPECTATOR; + else if (ent->s.modelindex != 255) + client->ps.pmove.pm_type = PM_GIB; + else if (ent->deadflag) + client->ps.pmove.pm_type = PM_DEAD; + else + client->ps.pmove.pm_type = PM_NORMAL; + + client->ps.pmove.gravity = sv_gravity->value; + pm.s = client->ps.pmove; + + for (i=0 ; i<3 ; i++) + { + pm.s.origin[i] = ent->s.origin[i]*8; + pm.s.velocity[i] = ent->velocity[i]*8; + } + + if (memcmp(&client->old_pmove, &pm.s, sizeof(pm.s))) + { + pm.snapinitial = true; + // gi.dprintf ("pmove changed!\n"); + } + + pm.cmd = *ucmd; + + pm.trace = PM_trace; // adds default parms + pm.pointcontents = gi.pointcontents; + + // perform a pmove + gi.Pmove (&pm); + + // save results of pmove + client->ps.pmove = pm.s; + client->old_pmove = pm.s; + + for (i=0 ; i<3 ; i++) + { + ent->s.origin[i] = pm.s.origin[i]*0.125; + ent->velocity[i] = pm.s.velocity[i]*0.125; + } + + VectorCopy (pm.mins, ent->mins); + VectorCopy (pm.maxs, ent->maxs); + + client->resp.cmd_angles[0] = SHORT2ANGLE(ucmd->angles[0]); + client->resp.cmd_angles[1] = SHORT2ANGLE(ucmd->angles[1]); + client->resp.cmd_angles[2] = SHORT2ANGLE(ucmd->angles[2]); + + if (ent->groundentity && !pm.groundentity && (pm.cmd.upmove >= 10) && (pm.waterlevel == 0)) + { + gi.sound(ent, CHAN_VOICE, gi.soundindex("*jump1.wav"), 1, ATTN_NORM, 0); + PlayerNoise(ent, ent->s.origin, PNOISE_SELF); + } + + ent->viewheight = pm.viewheight; + ent->waterlevel = pm.waterlevel; + ent->watertype = pm.watertype; + ent->groundentity = pm.groundentity; + if (pm.groundentity) + ent->groundentity_linkcount = pm.groundentity->linkcount; + + if (ent->deadflag) + { + client->ps.viewangles[ROLL] = 40; + client->ps.viewangles[PITCH] = -15; + client->ps.viewangles[YAW] = client->killer_yaw; + } + else + { + VectorCopy (pm.viewangles, client->v_angle); + VectorCopy (pm.viewangles, client->ps.viewangles); + } + + +//ZOID + if (client->ctf_grapple) + CTFGrapplePull(client->ctf_grapple); +//ZOID + + gi.linkentity (ent); + + if (ent->movetype != MOVETYPE_NOCLIP) + G_TouchTriggers (ent); + + // touch other objects + for (i=0 ; itouch) + continue; + other->touch (other, ent, NULL, NULL); + } + + } + client->oldbuttons = client->buttons; + client->buttons = ucmd->buttons; + client->latched_buttons |= client->buttons & ~client->oldbuttons; + + // save light level the player is standing on for + // monster sighting AI + ent->light_level = ucmd->lightlevel; + + // fire weapon from final position if needed +/* if (client->latched_buttons & BUTTON_ATTACK +//ZOID + && ent->movetype != MOVETYPE_NOCLIP +//ZOID + ) + { + if (!client->weapon_thunk) + { + client->weapon_thunk = true; + Think_Weapon (ent); + } + }*/ + // fire weapon from final position if needed + if (client->latched_buttons & BUTTON_ATTACK) + { + if (client->resp.spectator) { + + client->latched_buttons = 0; + + if (client->chase_target) { + client->chase_target = NULL; + client->ps.pmove.pm_flags &= ~PMF_NO_PREDICTION; + } else + GetChaseTarget(ent); + + } else if (!client->weapon_thunk) { + client->weapon_thunk = true; + Think_Weapon (ent); + } + } + + if (client->resp.spectator) { + if (ucmd->upmove >= 10) { + if (!(client->ps.pmove.pm_flags & PMF_JUMP_HELD)) { + client->ps.pmove.pm_flags |= PMF_JUMP_HELD; + if (client->chase_target) + ChaseNext(ent); + else + GetChaseTarget(ent); + } + } else + client->ps.pmove.pm_flags &= ~PMF_JUMP_HELD; + } + +//ZOID +//regen tech + CTFApplyRegeneration(ent); +//ZOID + + // update chase cam if being followed + for (i = 1; i <= maxclients->value; i++) { + other = g_edicts + i; + if (other->inuse && ent->client->chase_target == other/*other->client->chase_target == ent*/) + UpdateChaseCam(ent/*other*/); + } + +} + + +/* +============== +ClientBeginServerFrame + +This will be called once for each server frame, before running +any other entities in the world. +============== +*/ +void ClientBeginServerFrame (edict_t *ent) +{ + gclient_t *client; + + if (level.intermissiontime) + return; + + client = ent->client; + + if (deathmatch->value && + client->pers.spectator != client->resp.spectator && + (level.time - client->respawn_time) >= 5) { + spectator_respawn(ent); + return; + } + + // run weapon animations if it hasn't been done by a ucmd_t + if (!client->weapon_thunk +//ZOID + && ent->movetype != MOVETYPE_NOCLIP +//ZOID + ) + Think_Weapon (ent); + else + client->weapon_thunk = false; + + if (ent->deadflag) + { + // wait for any button just going down + if ( level.time > client->respawn_time) + { + if (client->latched_buttons || + (deathmatch->value && ((int)dmflags->value & DF_FORCE_RESPAWN) ) ) + { + respawn(ent); + client->latched_buttons = 0; + } + } + return; + } + + // add player trail so monsters can follow + if (!deathmatch->value) + if (!visible (ent, PlayerTrail_LastSpot() ) ) + PlayerTrail_Add (ent->s.old_origin); + + client->latched_buttons = 0; +} diff --git a/src/p_hud.c b/src/p_hud.c new file mode 100644 index 0000000..8f2ae7f --- /dev/null +++ b/src/p_hud.c @@ -0,0 +1,585 @@ +#include "g_local.h" +#include "bot.h" + +/* +====================================================================== + +INTERMISSION + +====================================================================== +*/ + +void MoveClientToIntermission (edict_t *ent) +{ + if(!(ent->svflags & SVF_MONSTER)) + { + ent->client->showscores = true; +// VectorCopy (level.intermission_origin, ent->s.origin); + ent->client->ps.pmove.origin[0] = level.intermission_origin[0]*8; + ent->client->ps.pmove.origin[1] = level.intermission_origin[1]*8; + ent->client->ps.pmove.origin[2] = level.intermission_origin[2]*8; + VectorCopy (level.intermission_angle, ent->client->ps.viewangles); + ent->client->ps.pmove.pm_type = PM_FREEZE; + ent->client->ps.gunindex = 0; + ent->client->ps.blend[3] = 0; + } + + VectorCopy (level.intermission_origin, ent->s.origin); + // clean up powerup info + ent->client->quad_framenum = 0; + ent->client->invincible_framenum = 0; + ent->client->breather_framenum = 0; + ent->client->enviro_framenum = 0; + ent->client->grenade_blew_up = false; + ent->client->grenade_time = 0; + + // RAFAEL + ent->client->quadfire_framenum = 0; + + // RAFAEL + ent->client->trap_blew_up = false; + ent->client->trap_time = 0; + + ent->viewheight = 0; + ent->s.modelindex = 0; + ent->s.modelindex2 = 0; + ent->s.modelindex3 = 0; + ent->s.modelindex = 0; + ent->s.effects = 0; + ent->s.sound = 0; + ent->solid = SOLID_NOT; + + // add the layout + + if (deathmatch->value && !(ent->svflags & SVF_MONSTER)) + { + DeathmatchScoreboardMessage (ent, NULL); + gi.unicast (ent, true); + } + +} +void SetLVChanged ( int i ); +void DelBots2(int i); +int GetNumbots ( void ); +int GetLVChanged ( void ); + +void BeginIntermission (edict_t *targ) +{ + int i; + edict_t *ent, *client; + + if (level.intermissiontime) + return; // allready activated + +//ZOID + if (deathmatch->value && ctf->value) + CTFCalcScores(); +//ZOID + +// game.autosaved = false; + + // respawn any dead clients +// for (i=0 ; ivalue ; i++) +// { +// client = g_edicts + 1 + i; +// if (!client->inuse) +// continue; +// if (client->health <= 0) +// respawn(client); +// } + + level.intermissiontime = level.time; + level.changemap = targ->map; + + // if on same unit, return immediately + if (!deathmatch->value && (targ->map && targ->map[0] != '*') ) + { // go immediately to the next level + level.exitintermission = 1; + return; + } + level.exitintermission = 0; + + // find an intermission spot + ent = G_Find (NULL, FOFS(classname), "info_player_intermission"); + if (!ent) + { // the map creator forgot to put in an intermission point... + ent = G_Find (NULL, FOFS(classname), "info_player_start"); + if (!ent) + ent = G_Find (NULL, FOFS(classname), "info_player_deathmatch"); + } + else + { // chose one of four spots + i = rand() & 3; + while (i--) + { + ent = G_Find (ent, FOFS(classname), "info_player_intermission"); + if (!ent) // wrap around the list + ent = G_Find (ent, FOFS(classname), "info_player_intermission"); + } + } + + VectorCopy (ent->s.origin, level.intermission_origin); + VectorCopy (ent->s.angles, level.intermission_angle); + + // move all clients to the intermission point + for (i=0 ; ivalue ; i++) + { + client = g_edicts + 1 + i; + if (!client->inuse) + continue; + MoveClientToIntermission (client); + } +} + + +/* +================== +DeathmatchScoreboardMessage + +================== +*/ +void DeathmatchScoreboardMessage (edict_t *ent, edict_t *killer) +{ + char entry[1024]; + char string[1400]; + int stringlength; + int i, j, k; + int sorted[MAX_CLIENTS]; + int sortedscores[MAX_CLIENTS]; + int score, total; + int picnum; + int x, y; + gclient_t *cl; + edict_t *cl_ent; + char *tag; + +//ZOID + if (ctf->value) { + CTFScoreboardMessage (ent, killer); + return; + } +//ZOID + + // sort the clients by score + total = 0; + for (i=0 ; iinuse) + continue; + score = game.clients[i].resp.score; + for (j=0 ; j sortedscores[j]) + break; + } + for (k=total ; k>j ; k--) + { + sorted[k] = sorted[k-1]; + sortedscores[k] = sortedscores[k-1]; + } + sorted[j] = i; + sortedscores[j] = score; + total++; + } + + // print level name and exit rules + string[0] = 0; + + stringlength = strlen(string); + + // add the clients in sorted order + if (total > 12) + total = 12; + + for (i=0 ; i=6) ? 160 : 0; + y = 32 + 32 * (i%6); + + // add a dogtag + if (cl_ent == ent) + tag = "tag1"; + else if (cl_ent == killer) + tag = "tag2"; + else + tag = NULL; + if (tag) + { + Com_sprintf (entry, sizeof(entry), + "xv %i yv %i picn %s ",x+32, y, tag); + j = strlen(entry); + if (stringlength + j > 1024) + break; + strcpy (string + stringlength, entry); + stringlength += j; + } + + // send the layout + Com_sprintf (entry, sizeof(entry), + "client %i %i %i %i %i %i ", + x, y, sorted[i], cl->resp.score, cl->ping, (level.framenum - cl->resp.enterframe)/600); + j = strlen(entry); + if (stringlength + j > 1024) + break; + strcpy (string + stringlength, entry); + stringlength += j; + } + + gi.WriteByte (svc_layout); + gi.WriteString (string); +} + + +/* +================== +DeathmatchScoreboard + +Draw instead of help message. +Note that it isn't that hard to overflow the 1400 byte message limit! +================== +*/ +void DeathmatchScoreboard (edict_t *ent) +{ + DeathmatchScoreboardMessage (ent, ent->enemy); + gi.unicast (ent, true); +} + + +/* +================== +Cmd_Score_f + +Display the scoreboard +================== +*/ +void Cmd_Score_f (edict_t *ent) +{ + ent->client->showinventory = false; + ent->client->showhelp = false; + +//ZOID + if (ent->client->menu) + PMenu_Close(ent); +//ZOID + + if (!deathmatch->value && !coop->value) + return; + + if (ent->client->showscores) + { + ent->client->showscores = false; + return; + } + + ent->client->showscores = true; + DeathmatchScoreboard (ent); +} + + +/* +================== +HelpComputer + +Draw help computer. +================== +*/ +void HelpComputer (edict_t *ent) +{ + char string[1024]; + char *sk; + + if (skill->value == 0) + sk = "easy"; + else if (skill->value == 1) + sk = "medium"; + else if (skill->value == 2) + sk = "hard"; + else + sk = "hard+"; + + // send the layout + Com_sprintf (string, sizeof(string), + "xv 32 yv 8 picn help " // background + "xv 202 yv 12 string2 \"%s\" " // skill + "xv 0 yv 24 cstring2 \"%s\" " // level name + "xv 0 yv 54 cstring2 \"%s\" " // help 1 + "xv 0 yv 110 cstring2 \"%s\" " // help 2 + "xv 50 yv 164 string2 \" kills goals secrets\" " + "xv 50 yv 172 string2 \"%3i/%3i %i/%i %i/%i\" ", + sk, + level.level_name, + game.helpmessage1, + game.helpmessage2, + level.killed_monsters, level.total_monsters, + level.found_goals, level.total_goals, + level.found_secrets, level.total_secrets); + + gi.WriteByte (svc_layout); + gi.WriteString (string); + gi.unicast (ent, true); +} + + +/* +================== +Cmd_Help_f + +Display the current help message +================== +*/ +void Cmd_Help_f (edict_t *ent) +{ + // this is for backwards compatability + if (deathmatch->value) + { + Cmd_Score_f (ent); + return; + } + + ent->client->showinventory = false; + ent->client->showscores = false; + + if (ent->client->showhelp && (ent->client->resp.game_helpchanged == game.helpchanged)) + { + ent->client->showhelp = false; + return; + } + + ent->client->showhelp = true; + ent->client->resp.helpchanged = 0; + HelpComputer (ent); +} + + +//======================================================================= + +/* +=============== +G_SetStats +=============== +*/ +void G_SetStats (edict_t *ent) +{ + gitem_t *item; + int index, cells; + int power_armor_type; + + // + // health + // + ent->client->ps.stats[STAT_HEALTH_ICON] = level.pic_health; + ent->client->ps.stats[STAT_HEALTH] = ent->health; + + // + // ammo + // + if (!ent->client->ammo_index /* || !ent->client->pers.inventory[ent->client->ammo_index] */) + { + ent->client->ps.stats[STAT_AMMO_ICON] = 0; + ent->client->ps.stats[STAT_AMMO] = 0; + } + else + { + item = &itemlist[ent->client->ammo_index]; + ent->client->ps.stats[STAT_AMMO_ICON] = gi.imageindex (item->icon); + ent->client->ps.stats[STAT_AMMO] = ent->client->pers.inventory[ent->client->ammo_index]; + } + + // + // armor + // + power_armor_type = PowerArmorType (ent); + if (power_armor_type) + { + cells = ent->client->pers.inventory[ITEM_INDEX(Fdi_CELLS/*FindItem ("cells")*/)]; + if (cells == 0) + { // ran out of cells for power armor + ent->flags &= ~FL_POWER_ARMOR; + gi.sound(ent, CHAN_ITEM, gi.soundindex("misc/power2.wav"), 1, ATTN_NORM, 0); + power_armor_type = 0;; + } + } + + index = ArmorIndex (ent); + if (power_armor_type && (!index || (level.framenum & 8) ) ) + { // flash between power armor and other armor icon + ent->client->ps.stats[STAT_ARMOR_ICON] = gi.imageindex ("i_powershield"); + ent->client->ps.stats[STAT_ARMOR] = cells; + } + else if (index) + { + item = GetItemByIndex (index); + ent->client->ps.stats[STAT_ARMOR_ICON] = gi.imageindex (item->icon); + ent->client->ps.stats[STAT_ARMOR] = ent->client->pers.inventory[index]; + } + else + { + ent->client->ps.stats[STAT_ARMOR_ICON] = 0; + ent->client->ps.stats[STAT_ARMOR] = 0; + } + + // + // pickup message + // + if (level.time > ent->client->pickup_msg_time) + { + ent->client->ps.stats[STAT_PICKUP_ICON] = 0; + ent->client->ps.stats[STAT_PICKUP_STRING] = 0; + } + + // + // timers + // + if (ent->client->quad_framenum > level.framenum) + { + ent->client->ps.stats[STAT_TIMER_ICON] = gi.imageindex ("p_quad"); + ent->client->ps.stats[STAT_TIMER] = (ent->client->quad_framenum - level.framenum)/10; + } + // RAFAEL + else if (ent->client->quadfire_framenum > level.framenum) + { + // note to self + // need to change imageindex + ent->client->ps.stats[STAT_TIMER_ICON] = gi.imageindex ("p_quadfire"); + ent->client->ps.stats[STAT_TIMER] = (ent->client->quadfire_framenum - level.framenum)/10; + } + else if (ent->client->invincible_framenum > level.framenum) + { + ent->client->ps.stats[STAT_TIMER_ICON] = gi.imageindex ("p_invulnerability"); + ent->client->ps.stats[STAT_TIMER] = (ent->client->invincible_framenum - level.framenum)/10; + } + else if (ent->client->enviro_framenum > level.framenum) + { + ent->client->ps.stats[STAT_TIMER_ICON] = gi.imageindex ("p_envirosuit"); + ent->client->ps.stats[STAT_TIMER] = (ent->client->enviro_framenum - level.framenum)/10; + } + else if (ent->client->breather_framenum > level.framenum) + { + ent->client->ps.stats[STAT_TIMER_ICON] = gi.imageindex ("p_rebreather"); + ent->client->ps.stats[STAT_TIMER] = (ent->client->breather_framenum - level.framenum)/10; + } + else + { + ent->client->ps.stats[STAT_TIMER_ICON] = 0; + ent->client->ps.stats[STAT_TIMER] = 0; + } + + // + // selected item + // + if (ent->client->pers.selected_item == -1) + ent->client->ps.stats[STAT_SELECTED_ICON] = 0; + else + ent->client->ps.stats[STAT_SELECTED_ICON] = gi.imageindex (itemlist[ent->client->pers.selected_item].icon); + + ent->client->ps.stats[STAT_SELECTED_ITEM] = ent->client->pers.selected_item; + + // + // layouts + // + ent->client->ps.stats[STAT_LAYOUTS] = 0; + + if (deathmatch->value) + { + if (ent->client->pers.health <= 0 || level.intermissiontime + || ent->client->showscores) + ent->client->ps.stats[STAT_LAYOUTS] |= 1; + if (ent->client->showinventory && ent->client->pers.health > 0) + ent->client->ps.stats[STAT_LAYOUTS] |= 2; + } + else + { + if (ent->client->showscores || ent->client->showhelp) + ent->client->ps.stats[STAT_LAYOUTS] |= 1; + if (ent->client->showinventory && ent->client->pers.health > 0) + ent->client->ps.stats[STAT_LAYOUTS] |= 2; + } + + // + // frags + // + ent->client->ps.stats[STAT_FRAGS] = ent->client->resp.score; + + // + // help icon / current weapon if not shown + // + if (ent->client->resp.helpchanged && (level.framenum&8) ) + ent->client->ps.stats[STAT_HELPICON] = gi.imageindex ("i_help"); + else if ( (ent->client->pers.hand == CENTER_HANDED || ent->client->ps.fov > 91) + && ent->client->pers.weapon) + ent->client->ps.stats[STAT_HELPICON] = gi.imageindex (ent->client->pers.weapon->icon); + else + ent->client->ps.stats[STAT_HELPICON] = 0; + +//ponpoko + if(ent->client->zc.aiming == 1) + { + ent->client->ps.stats[STAT_SIGHT_PIC] = gi.imageindex ("zsight"); + } + else if(ent->client->zc.aiming == 3) + { + if(ent->client->zc.lockon) ent->client->ps.stats[STAT_SIGHT_PIC] = gi.imageindex ("zsight_l1"); + else ent->client->ps.stats[STAT_SIGHT_PIC] = gi.imageindex ("zsight_l0"); + } + else ent->client->ps.stats[STAT_SIGHT_PIC] = 0;//gi.imageindex ("i_help"/*"zsight"*/);; +//ponpoko + +//ZOID + SetCTFStats(ent); +//ZOID + +} + + +/* +=============== +G_CheckChaseStats +=============== +*/ +void G_CheckChaseStats (edict_t *ent) +{ + int i; + gclient_t *cl; + + for (i = 1; i <= maxclients->value; i++) { + cl = g_edicts[i].client; + if (!g_edicts[i].inuse || cl->chase_target != ent) + continue; + memcpy(cl->ps.stats, ent->client->ps.stats, sizeof(cl->ps.stats)); + G_SetSpectatorStats(g_edicts + i); + } +} + +/* +=============== +G_SetSpectatorStats +=============== +*/ +void G_SetSpectatorStats (edict_t *ent) +{ + gclient_t *cl = ent->client; + + if (!cl->chase_target) + G_SetStats (ent); + + cl->ps.stats[STAT_SPECTATOR] = 1; + + // layouts are independant in spectator + cl->ps.stats[STAT_LAYOUTS] = 0; + if (cl->pers.health <= 0 || level.intermissiontime || cl->showscores) + cl->ps.stats[STAT_LAYOUTS] |= 1; + if (cl->showinventory && cl->pers.health > 0) + cl->ps.stats[STAT_LAYOUTS] |= 2; + + if (cl->chase_target && cl->chase_target->inuse) + cl->ps.stats[STAT_CHASE] = CS_PLAYERSKINS + + (cl->chase_target - g_edicts) - 1; + else + cl->ps.stats[STAT_CHASE] = 0; +} + diff --git a/src/p_menu.c b/src/p_menu.c new file mode 100644 index 0000000..ba0bc89 --- /dev/null +++ b/src/p_menu.c @@ -0,0 +1,196 @@ +#include "g_local.h" + +void PMenu_Open(edict_t *ent, pmenu_t *entries, int cur, int num) +{ + pmenuhnd_t *hnd; + pmenu_t *p; + int i; + + if (!ent->client) + return; + + if (ent->client->menu) { + gi.dprintf("warning, ent already has a menu\n"); + PMenu_Close(ent); + } + + hnd = malloc(sizeof(*hnd)); + + hnd->entries = entries; + hnd->num = num; + + if (cur < 0 || !entries[cur].SelectFunc) { + for (i = 0, p = entries; i < num; i++, p++) + if (p->SelectFunc) + break; + } else + i = cur; + + if (i >= num) + hnd->cur = -1; + else + hnd->cur = i; + + ent->client->showscores = true; + ent->client->inmenu = true; + ent->client->menu = hnd; + + if(!(ent->svflags & SVF_MONSTER)) + { + PMenu_Update(ent); + gi.unicast (ent, true); + } +} + +void PMenu_Close(edict_t *ent) +{ + if (!ent->client->menu) + return; + + free(ent->client->menu); + ent->client->menu = NULL; + ent->client->showscores = false; +} + +void PMenu_Update(edict_t *ent) +{ + char string[1400]; + int i; + pmenu_t *p; + int x; + pmenuhnd_t *hnd; + char *t; + qboolean alt = false; + + if (!ent->client->menu) { + gi.dprintf("warning: ent has no menu\n"); + return; + } + + hnd = ent->client->menu; + + strcpy(string, "xv 32 yv 8 picn inventory "); + + for (i = 0, p = hnd->entries; i < hnd->num; i++, p++) { + if (!p->text || !*(p->text)) + continue; // blank line + t = p->text; + if (*t == '*') { + alt = true; + t++; + } + sprintf(string + strlen(string), "yv %d ", 32 + i * 8); + if (p->align == PMENU_ALIGN_CENTER) + x = 196/2 - strlen(t)*4 + 64; + else if (p->align == PMENU_ALIGN_RIGHT) + x = 64 + (196 - strlen(t)*8); + else + x = 64; + + sprintf(string + strlen(string), "xv %d ", + x - ((hnd->cur == i) ? 8 : 0)); + + if (hnd->cur == i) + sprintf(string + strlen(string), "string2 \"\x0d%s\" ", t); + else if (alt) + sprintf(string + strlen(string), "string2 \"%s\" ", t); + else + sprintf(string + strlen(string), "string \"%s\" ", t); + alt = false; + } + + gi.WriteByte (svc_layout); + gi.WriteString (string); +} + +void PMenu_Next(edict_t *ent) +{ + pmenuhnd_t *hnd; + int i; + pmenu_t *p; + + if (!ent->client->menu) { + gi.dprintf("warning: ent has no menu\n"); + return; + } + + hnd = ent->client->menu; + + if (hnd->cur < 0) + return; // no selectable entries + + i = hnd->cur; + p = hnd->entries + hnd->cur; + do { + i++, p++; + if (i == hnd->num) + i = 0, p = hnd->entries; + if (p->SelectFunc) + break; + } while (i != hnd->cur); + + hnd->cur = i; + if(!(ent->svflags & SVF_MONSTER)) + { + PMenu_Update(ent); + gi.unicast (ent, true); + } +} + +void PMenu_Prev(edict_t *ent) +{ + pmenuhnd_t *hnd; + int i; + pmenu_t *p; + + if (!ent->client->menu) { + gi.dprintf("warning: ent has no menu\n"); + return; + } + + hnd = ent->client->menu; + + if (hnd->cur < 0) + return; // no selectable entries + + i = hnd->cur; + p = hnd->entries + hnd->cur; + do { + if (i == 0) { + i = hnd->num - 1; + p = hnd->entries + i; + } else + i--, p--; + if (p->SelectFunc) + break; + } while (i != hnd->cur); + + hnd->cur = i; + + if(!(ent->svflags & SVF_MONSTER)) + { + PMenu_Update(ent); + gi.unicast (ent, true); + } +} + +void PMenu_Select(edict_t *ent) +{ + pmenuhnd_t *hnd; + pmenu_t *p; + + if (!ent->client->menu) { + gi.dprintf("warning: ent has no menu\n"); + return; + } + + hnd = ent->client->menu; + + if (hnd->cur < 0) + return; // no selectable entries + + p = hnd->entries + hnd->cur; + + if (p->SelectFunc) + p->SelectFunc(ent, p); +} diff --git a/src/p_menu.h b/src/p_menu.h new file mode 100644 index 0000000..5b3da42 --- /dev/null +++ b/src/p_menu.h @@ -0,0 +1,26 @@ + +enum { + PMENU_ALIGN_LEFT, + PMENU_ALIGN_CENTER, + PMENU_ALIGN_RIGHT +}; + +typedef struct pmenuhnd_s { + struct pmenu_s *entries; + int cur; + int num; +} pmenuhnd_t; + +typedef struct pmenu_s { + char *text; + int align; + void *arg; + void (*SelectFunc)(edict_t *ent, struct pmenu_s *entry); +} pmenu_t; + +void PMenu_Open(edict_t *ent, pmenu_t *entries, int cur, int num); +void PMenu_Close(edict_t *ent); +void PMenu_Update(edict_t *ent); +void PMenu_Next(edict_t *ent); +void PMenu_Prev(edict_t *ent); +void PMenu_Select(edict_t *ent); diff --git a/src/p_trail.c b/src/p_trail.c new file mode 100644 index 0000000..b8f72ba --- /dev/null +++ b/src/p_trail.c @@ -0,0 +1,127 @@ +#include "g_local.h" + + +/* +============================================================================== + +PLAYER TRAIL + +============================================================================== + +This is a circular list containing the a list of points of where +the player has been recently. It is used by monsters for pursuit. + +.origin the spot +.owner forward link +.aiment backward link +*/ + + +#define TRAIL_LENGTH 8 + +edict_t *trail[TRAIL_LENGTH]; +int trail_head; +qboolean trail_active = false; + +#define NEXT(n) (((n) + 1) & (TRAIL_LENGTH - 1)) +#define PREV(n) (((n) - 1) & (TRAIL_LENGTH - 1)) + + +void PlayerTrail_Init (void) +{ + int n; + + if (deathmatch->value /* FIXME || coop */) + return; + + for (n = 0; n < TRAIL_LENGTH; n++) + { + trail[n] = G_Spawn(); + trail[n]->classname = "player_trail"; + } + + trail_head = 0; + trail_active = true; +} + + +void PlayerTrail_Add (vec3_t spot) +{ + vec3_t temp; + + if (!trail_active) + return; + + VectorCopy (spot, trail[trail_head]->s.origin); + + trail[trail_head]->timestamp = level.time; + + VectorSubtract (spot, trail[PREV(trail_head)]->s.origin, temp); + trail[trail_head]->s.angles[1] = vectoyaw (temp); + + trail_head = NEXT(trail_head); +} + + +void PlayerTrail_New (vec3_t spot) +{ + if (!trail_active) + return; + + PlayerTrail_Init (); + PlayerTrail_Add (spot); +} + + +edict_t *PlayerTrail_PickFirst (edict_t *self) +{ + int marker; + int n; + + if (!trail_active) + return NULL; + + for (marker = trail_head, n = TRAIL_LENGTH; n; n--) + { + if(trail[marker]->timestamp <= self->monsterinfo.trail_time) + marker = NEXT(marker); + else + break; + } + + if (visible(self, trail[marker])) + { + return trail[marker]; + } + + if (visible(self, trail[PREV(marker)])) + { + return trail[PREV(marker)]; + } + + return trail[marker]; +} + +edict_t *PlayerTrail_PickNext (edict_t *self) +{ + int marker; + int n; + + if (!trail_active) + return NULL; + + for (marker = trail_head, n = TRAIL_LENGTH; n; n--) + { + if(trail[marker]->timestamp <= self->monsterinfo.trail_time) + marker = NEXT(marker); + else + break; + } + + return trail[marker]; +} + +edict_t *PlayerTrail_LastSpot (void) +{ + return trail[trail_head]; +} diff --git a/src/p_view.c b/src/p_view.c new file mode 100644 index 0000000..60dfe5c --- /dev/null +++ b/src/p_view.c @@ -0,0 +1,1552 @@ + +#include "g_local.h" +#include "m_player.h" +#include "bot.h" + + +static edict_t *current_player; +static gclient_t *current_client; + +static vec3_t forward, right, up; +float xyspeed; + +float bobmove; +int bobcycle; // odd cycles are right foot going forward +float bobfracsin; // sin(bobfrac*M_PI) + +/* +=============== +SV_CalcRoll + +=============== +*/ +float SV_CalcRoll (vec3_t angles, vec3_t velocity) +{ + float sign; + float side; + float value; + + side = DotProduct (velocity, right); + sign = side < 0 ? -1 : 1; + side = fabs(side); + + value = sv_rollangle->value; + + if (side < sv_rollspeed->value) + side = side * value / sv_rollspeed->value; + else + side = value; + + return side*sign; + +} + + +/* +=============== +P_DamageFeedback + +Handles color blends and view kicks +=============== +*/ +void P_DamageFeedback (edict_t *player) +{ + gclient_t *client; + float side; + float realcount, count, kick; + vec3_t v; + int r, l; + static vec3_t power_color = {0.0, 1.0, 0.0}; + static vec3_t acolor = {1.0, 1.0, 1.0}; + static vec3_t bcolor = {1.0, 0.0, 0.0}; + + client = player->client; + + // flash the backgrounds behind the status numbers + client->ps.stats[STAT_FLASHES] = 0; + if (client->damage_blood) + client->ps.stats[STAT_FLASHES] |= 1; + if (client->damage_armor && !(player->flags & FL_GODMODE) && (client->invincible_framenum <= level.framenum)) + client->ps.stats[STAT_FLASHES] |= 2; + + // total points of damage shot at the player this frame + count = (client->damage_blood + client->damage_armor + client->damage_parmor); + if (count == 0) + return; // didn't take any damage + + // start a pain animation if still in the player model + if (client->anim_priority < ANIM_PAIN && player->s.modelindex == 255) + { + static int i; + + client->anim_priority = ANIM_PAIN; + if (client->ps.pmove.pm_flags & PMF_DUCKED) + { + player->s.frame = FRAME_crpain1-1; + client->anim_end = FRAME_crpain4; + } + else + { + i = (i+1)%3; + switch (i) + { + case 0: + player->s.frame = FRAME_pain101-1; + client->anim_end = FRAME_pain104; + break; + case 1: + player->s.frame = FRAME_pain201-1; + client->anim_end = FRAME_pain204; + break; + case 2: + player->s.frame = FRAME_pain301-1; + client->anim_end = FRAME_pain304; + break; + } + } + } + + realcount = count; + if (count < 10) + count = 10; // always make a visible effect + + // play an apropriate pain sound + if ((level.time > player->pain_debounce_time) && !(player->flags & FL_GODMODE) && (client->invincible_framenum <= level.framenum)) + { + r = 1 + (rand()&1); + player->pain_debounce_time = level.time + 0.7; + if (player->health < 25) + l = 25; + else if (player->health < 50) + l = 50; + else if (player->health < 75) + l = 75; + else + l = 100; + gi.sound (player, CHAN_VOICE, gi.soundindex(va("*pain%i_%i.wav", l, r)), 1, ATTN_NORM, 0); + } + + // the total alpha of the blend is always proportional to count + if (client->damage_alpha < 0) + client->damage_alpha = 0; + client->damage_alpha += count*0.01; + if (client->damage_alpha < 0.2) + client->damage_alpha = 0.2; + if (client->damage_alpha > 0.6) + client->damage_alpha = 0.6; // don't go too saturated + + // the color of the blend will vary based on how much was absorbed + // by different armors + VectorClear (v); + if (client->damage_parmor) + VectorMA (v, (float)client->damage_parmor/realcount, power_color, v); + if (client->damage_armor) + VectorMA (v, (float)client->damage_armor/realcount, acolor, v); + if (client->damage_blood) + VectorMA (v, (float)client->damage_blood/realcount, bcolor, v); + VectorCopy (v, client->damage_blend); + + + // + // calculate view angle kicks + // + kick = abs(client->damage_knockback); + if (kick && player->health > 0) // kick of 0 means no view adjust at all + { + kick = kick * 100 / player->health; + + if (kick < count*0.5) + kick = count*0.5; + if (kick > 50) + kick = 50; + + VectorSubtract (client->damage_from, player->s.origin, v); + VectorNormalize (v); + + side = DotProduct (v, right); + client->v_dmg_roll = kick*side*0.3; + + side = -DotProduct (v, forward); + client->v_dmg_pitch = kick*side*0.3; + + client->v_dmg_time = level.time + DAMAGE_TIME; + } + + // + // clear totals + // + client->damage_blood = 0; + client->damage_armor = 0; + client->damage_parmor = 0; + client->damage_knockback = 0; +} + + + + +/* +=============== +SV_CalcViewOffset + +Auto pitching on slopes? + + fall from 128: 400 = 160000 + fall from 256: 580 = 336400 + fall from 384: 720 = 518400 + fall from 512: 800 = 640000 + fall from 640: 960 = + + damage = deltavelocity*deltavelocity * 0.0001 + +=============== +*/ +void SV_CalcViewOffset (edict_t *ent) +{ + float *angles; + float bob; + float ratio; + float delta; + vec3_t v; + + +//=================================== + + // base angles + angles = ent->client->ps.kick_angles; + + // if dead, fix the angle and don't add any kick + if (ent->deadflag) + { + VectorClear (angles); + + ent->client->ps.viewangles[ROLL] = 40; + ent->client->ps.viewangles[PITCH] = -15; + ent->client->ps.viewangles[YAW] = ent->client->killer_yaw; + } + else + { + // add angles based on weapon kick + + VectorCopy (ent->client->kick_angles, angles); + + // add angles based on damage kick + + ratio = (ent->client->v_dmg_time - level.time) / DAMAGE_TIME; + if (ratio < 0) + { + ratio = 0; + ent->client->v_dmg_pitch = 0; + ent->client->v_dmg_roll = 0; + } + angles[PITCH] += ratio * ent->client->v_dmg_pitch; + angles[ROLL] += ratio * ent->client->v_dmg_roll; + + // add pitch based on fall kick + + ratio = (ent->client->fall_time - level.time) / FALL_TIME; + if (ratio < 0) + ratio = 0; + angles[PITCH] += ratio * ent->client->fall_value; + + // add angles based on velocity + + delta = DotProduct (ent->velocity, forward); + angles[PITCH] += delta*run_pitch->value; + + delta = DotProduct (ent->velocity, right); + angles[ROLL] += delta*run_roll->value; + + // add angles based on bob + + delta = bobfracsin * bob_pitch->value * xyspeed; + if (ent->client->ps.pmove.pm_flags & PMF_DUCKED) + delta *= 6; // crouching + angles[PITCH] += delta; + delta = bobfracsin * bob_roll->value * xyspeed; + if (ent->client->ps.pmove.pm_flags & PMF_DUCKED) + delta *= 6; // crouching + if (bobcycle & 1) + delta = -delta; + angles[ROLL] += delta; + } + +//=================================== + + // base origin + + VectorClear (v); + + // add view height + + v[2] += ent->viewheight; + + // add fall height + + ratio = (ent->client->fall_time - level.time) / FALL_TIME; + if (ratio < 0) + ratio = 0; + v[2] -= ratio * ent->client->fall_value * 0.4; + + // add bob height + + bob = bobfracsin * xyspeed * bob_up->value; + if (bob > 6) + bob = 6; + //gi.DebugGraph (bob *2, 255); + v[2] += bob; + + // add kick offset + + VectorAdd (v, ent->client->kick_origin, v); + + // absolutely bound offsets + // so the view can never be outside the player box + + if (v[0] < -14) + v[0] = -14; + else if (v[0] > 14) + v[0] = 14; + if (v[1] < -14) + v[1] = -14; + else if (v[1] > 14) + v[1] = 14; + if (v[2] < -22) + v[2] = -22; + else if (v[2] > 30) + v[2] = 30; + + VectorCopy (v, ent->client->ps.viewoffset); +} + +/* +============== +SV_CalcGunOffset +============== +*/ +void SV_CalcGunOffset (edict_t *ent) +{ + int i; + float delta; + + // gun angles from bobbing + ent->client->ps.gunangles[ROLL] = xyspeed * bobfracsin * 0.005; + ent->client->ps.gunangles[YAW] = xyspeed * bobfracsin * 0.01; + if (bobcycle & 1) + { + ent->client->ps.gunangles[ROLL] = -ent->client->ps.gunangles[ROLL]; + ent->client->ps.gunangles[YAW] = -ent->client->ps.gunangles[YAW]; + } + + ent->client->ps.gunangles[PITCH] = xyspeed * bobfracsin * 0.005; + + // gun angles from delta movement + for (i=0 ; i<3 ; i++) + { + delta = ent->client->oldviewangles[i] - ent->client->ps.viewangles[i]; + if (delta > 180) + delta -= 360; + if (delta < -180) + delta += 360; + if (delta > 45) + delta = 45; + if (delta < -45) + delta = -45; + if (i == YAW) + ent->client->ps.gunangles[ROLL] += 0.1*delta; + ent->client->ps.gunangles[i] += 0.2 * delta; + } + + // gun height + VectorClear (ent->client->ps.gunoffset); +// ent->ps->gunorigin[2] += bob; + + // gun_x / gun_y / gun_z are development tools + for (i=0 ; i<3 ; i++) + { + ent->client->ps.gunoffset[i] += forward[i]*(gun_y->value); + ent->client->ps.gunoffset[i] += right[i]*gun_x->value; + ent->client->ps.gunoffset[i] += up[i]* (-gun_z->value); + } +} + + +/* +============= +SV_AddBlend +============= +*/ +void SV_AddBlend (float r, float g, float b, float a, float *v_blend) +{ + float a2, a3; + + if (a <= 0) + return; + a2 = v_blend[3] + (1-v_blend[3])*a; // new total alpha + a3 = v_blend[3]/a2; // fraction of color from old + + v_blend[0] = v_blend[0]*a3 + r*(1-a3); + v_blend[1] = v_blend[1]*a3 + g*(1-a3); + v_blend[2] = v_blend[2]*a3 + b*(1-a3); + v_blend[3] = a2; +} + + +/* +============= +SV_CalcBlend +============= +*/ +void SV_CalcBlend (edict_t *ent) +{ + int contents; + vec3_t vieworg; + int remaining; + + ent->client->ps.blend[0] = ent->client->ps.blend[1] = + ent->client->ps.blend[2] = ent->client->ps.blend[3] = 0; + + // add for contents + VectorAdd (ent->s.origin, ent->client->ps.viewoffset, vieworg); + contents = gi.pointcontents (vieworg); + if (contents & (CONTENTS_LAVA|CONTENTS_SLIME|CONTENTS_WATER) ) + ent->client->ps.rdflags |= RDF_UNDERWATER; + else + ent->client->ps.rdflags &= ~RDF_UNDERWATER; + + if (contents & (CONTENTS_SOLID|CONTENTS_LAVA)) + SV_AddBlend (1.0, 0.3, 0.0, 0.6, ent->client->ps.blend); + else if (contents & CONTENTS_SLIME) + SV_AddBlend (0.0, 0.1, 0.05, 0.6, ent->client->ps.blend); + else if (contents & CONTENTS_WATER) + SV_AddBlend (0.5, 0.3, 0.2, 0.4, ent->client->ps.blend); + + // add for powerups + if (ent->client->quad_framenum > level.framenum) + { + remaining = ent->client->quad_framenum - level.framenum; + if (remaining == 30) // beginning to fade + gi.sound(ent, CHAN_ITEM, gi.soundindex("items/damage2.wav"), 1, ATTN_NORM, 0); + if (remaining > 30 || (remaining & 4) ) + SV_AddBlend (0, 0, 1, 0.08, ent->client->ps.blend); + } + // RAFAEL + else if (ent->client->quadfire_framenum > level.framenum) + { + remaining = ent->client->quadfire_framenum - level.framenum; + if (remaining == 30) // beginning to fade + gi.sound(ent, CHAN_ITEM, gi.soundindex("items/quadfire2.wav"), 1, ATTN_NORM, 0); + if (remaining > 30 || (remaining & 4) ) + SV_AddBlend (1, 0.2, 0.5, 0.08, ent->client->ps.blend); + } + else if (ent->client->invincible_framenum > level.framenum) + { + remaining = ent->client->invincible_framenum - level.framenum; + if (remaining == 30) // beginning to fade + gi.sound(ent, CHAN_ITEM, gi.soundindex("items/protect2.wav"), 1, ATTN_NORM, 0); + if (remaining > 30 || (remaining & 4) ) + SV_AddBlend (1, 1, 0, 0.08, ent->client->ps.blend); + } + else if (ent->client->enviro_framenum > level.framenum) + { + remaining = ent->client->enviro_framenum - level.framenum; + if (remaining == 30) // beginning to fade + gi.sound(ent, CHAN_ITEM, gi.soundindex("items/airout.wav"), 1, ATTN_NORM, 0); + if (remaining > 30 || (remaining & 4) ) + SV_AddBlend (0, 1, 0, 0.08, ent->client->ps.blend); + } + else if (ent->client->breather_framenum > level.framenum) + { + remaining = ent->client->breather_framenum - level.framenum; + if (remaining == 30) // beginning to fade + gi.sound(ent, CHAN_ITEM, gi.soundindex("items/airout.wav"), 1, ATTN_NORM, 0); + if (remaining > 30 || (remaining & 4) ) + SV_AddBlend (0.4, 1, 0.4, 0.04, ent->client->ps.blend); + } + + // add for damage + if (ent->client->damage_alpha > 0) + SV_AddBlend (ent->client->damage_blend[0],ent->client->damage_blend[1] + ,ent->client->damage_blend[2], ent->client->damage_alpha, ent->client->ps.blend); + + if (ent->client->bonus_alpha > 0) + SV_AddBlend (0.85, 0.7, 0.3, ent->client->bonus_alpha, ent->client->ps.blend); + + // drop the damage value + ent->client->damage_alpha -= 0.06; + if (ent->client->damage_alpha < 0) + ent->client->damage_alpha = 0; + + // drop the bonus value + ent->client->bonus_alpha -= 0.1; + if (ent->client->bonus_alpha < 0) + ent->client->bonus_alpha = 0; +} + + +/* +================= +P_FallingDamage +================= +*/ +void P_FallingDamage (edict_t *ent) +{ + float delta; + int damage; + vec3_t dir; + + if (ent->s.modelindex != 255) + return; // not in the player model + + if (ent->movetype == MOVETYPE_NOCLIP) + return; + + if ((ent->client->oldvelocity[2] < 0) && (ent->velocity[2] > ent->client->oldvelocity[2]) && (!ent->groundentity)) + { + delta = ent->client->oldvelocity[2]; + } + else + { + if (!ent->groundentity) + return; + delta = ent->velocity[2] - ent->client->oldvelocity[2]; + } + delta = delta*delta * 0.0001; + +//ZOID + // never take damage if just release grapple or on grapple + if (level.time - ent->client->ctf_grapplereleasetime <= FRAMETIME * 2 || + (ent->client->ctf_grapple && + ent->client->ctf_grapplestate > CTF_GRAPPLE_STATE_FLY)) + return; +//ZOID + + // never take falling damage if completely underwater + if (ent->waterlevel == 3) + return; + if (ent->waterlevel == 2) + delta *= 0.25; + if (ent->waterlevel == 1) + delta *= 0.5; + + if (delta < 1) + return; + + if (delta < 15) + { + ent->s.event = EV_FOOTSTEP; + PlayerNoise(ent, ent->s.origin, PNOISE_SELF); //ponko + return; + } + + ent->client->fall_value = delta*0.5; + if (ent->client->fall_value > 40) + ent->client->fall_value = 40; + ent->client->fall_time = level.time + FALL_TIME; + + if (delta > 30) + { + if (ent->health > 0) + { + if (delta >= 55) + ent->s.event = EV_FALLFAR; + else + ent->s.event = EV_FALL; + PlayerNoise(ent, ent->s.origin, PNOISE_SELF); //ponko + } + ent->pain_debounce_time = level.time; // no normal pain sound + damage = (delta-30)/2; + if (damage < 1) + damage = 1; + VectorSet (dir, 0, 0, 1); + + if (!deathmatch->value || !((int)dmflags->value & DF_NO_FALLING) ) + T_Damage (ent, world, world, dir, ent->s.origin, vec3_origin, damage, 0, 0, MOD_FALLING); + } + else + { + ent->s.event = EV_FALLSHORT; + PlayerNoise(ent, ent->s.origin, PNOISE_SELF); //ponko + return; + } +} + + + +/* +============= +P_WorldEffects +============= +*/ +void P_WorldEffects (void) +{ + qboolean breather; + qboolean envirosuit; + int waterlevel, old_waterlevel; + + if (current_player->movetype == MOVETYPE_NOCLIP) + { + current_player->air_finished = level.time + 12; // don't need air + return; + } + + waterlevel = current_player->waterlevel; + old_waterlevel = current_client->old_waterlevel; + current_client->old_waterlevel = waterlevel; + + breather = current_client->breather_framenum > level.framenum; + envirosuit = current_client->enviro_framenum > level.framenum; + + // + // if just entered a water volume, play a sound + // + if (!old_waterlevel && waterlevel) + { + PlayerNoise(current_player, current_player->s.origin, PNOISE_SELF); + if (current_player->watertype & CONTENTS_LAVA) + gi.sound (current_player, CHAN_BODY, gi.soundindex("player/lava_in.wav"), 1, ATTN_NORM, 0); + else if (current_player->watertype & CONTENTS_SLIME) + gi.sound (current_player, CHAN_BODY, gi.soundindex("player/watr_in.wav"), 1, ATTN_NORM, 0); + else if (current_player->watertype & CONTENTS_WATER) + gi.sound (current_player, CHAN_BODY, gi.soundindex("player/watr_in.wav"), 1, ATTN_NORM, 0); + current_player->flags |= FL_INWATER; + + // clear damage_debounce, so the pain sound will play immediately + current_player->damage_debounce_time = level.time - 1; + } + + // + // if just completely exited a water volume, play a sound + // + if (old_waterlevel && ! waterlevel) + { + PlayerNoise(current_player, current_player->s.origin, PNOISE_SELF); + gi.sound (current_player, CHAN_BODY, gi.soundindex("player/watr_out.wav"), 1, ATTN_NORM, 0); + current_player->flags &= ~FL_INWATER; + } + + // + // check for head just going under water + // + if (old_waterlevel != 3 && waterlevel == 3) + { + gi.sound (current_player, CHAN_BODY, gi.soundindex("player/watr_un.wav"), 1, ATTN_NORM, 0); + } + + // + // check for head just coming out of water + // + if (old_waterlevel == 3 && waterlevel != 3) + { + if (current_player->air_finished < level.time) + { // gasp for air + gi.sound (current_player, CHAN_VOICE, gi.soundindex("player/gasp1.wav"), 1, ATTN_NORM, 0); + PlayerNoise(current_player, current_player->s.origin, PNOISE_SELF); + } + else if (current_player->air_finished < level.time + 11) + { // just break surface + gi.sound (current_player, CHAN_VOICE, gi.soundindex("player/gasp2.wav"), 1, ATTN_NORM, 0); + } + } + + // + // check for drowning + // + if (waterlevel == 3) + { + // breather or envirosuit give air + if (breather || envirosuit) + { + current_player->air_finished = level.time + 10; + + if (((int)(current_client->breather_framenum - level.framenum) % 25) == 0) + { + if (!current_client->breather_sound) + gi.sound (current_player, CHAN_AUTO, gi.soundindex("player/u_breath1.wav"), 1, ATTN_NORM, 0); + else + gi.sound (current_player, CHAN_AUTO, gi.soundindex("player/u_breath2.wav"), 1, ATTN_NORM, 0); + current_client->breather_sound ^= 1; + PlayerNoise(current_player, current_player->s.origin, PNOISE_SELF); + //FIXME: release a bubble? + } + } + + // if out of air, start drowning + if (current_player->air_finished < level.time) + { // drown! + if (current_player->client->next_drown_time < level.time + && current_player->health > 0) + { + current_player->client->next_drown_time = level.time + 1; + + // take more damage the longer underwater + current_player->dmg += 2; + if (current_player->dmg > 15) + current_player->dmg = 15; + + // play a gurp sound instead of a normal pain sound + if (current_player->health <= current_player->dmg) + gi.sound (current_player, CHAN_VOICE, gi.soundindex("player/drown1.wav"), 1, ATTN_NORM, 0); + else if (rand()&1) + gi.sound (current_player, CHAN_VOICE, gi.soundindex("*gurp1.wav"), 1, ATTN_NORM, 0); + else + gi.sound (current_player, CHAN_VOICE, gi.soundindex("*gurp2.wav"), 1, ATTN_NORM, 0); + + current_player->pain_debounce_time = level.time; + + T_Damage (current_player, world, world, vec3_origin, current_player->s.origin, vec3_origin, current_player->dmg, 0, DAMAGE_NO_ARMOR, MOD_WATER); + } + } + } + else + { + current_player->air_finished = level.time + 12; + current_player->dmg = 2; + } + + // + // check for sizzle damage + // + if (waterlevel && (current_player->watertype&(CONTENTS_LAVA|CONTENTS_SLIME)) ) + { + if (current_player->watertype & CONTENTS_LAVA) + { + if (current_player->health > 0 + && current_player->pain_debounce_time <= level.time + && current_client->invincible_framenum < level.framenum) + { + if (rand()&1) + gi.sound (current_player, CHAN_VOICE, gi.soundindex("player/burn1.wav"), 1, ATTN_NORM, 0); + else + gi.sound (current_player, CHAN_VOICE, gi.soundindex("player/burn2.wav"), 1, ATTN_NORM, 0); + current_player->pain_debounce_time = level.time + 1; + } + + if (envirosuit) // take 1/3 damage with envirosuit + T_Damage (current_player, world, world, vec3_origin, current_player->s.origin, vec3_origin, 1*waterlevel, 0, 0, MOD_LAVA); + else + T_Damage (current_player, world, world, vec3_origin, current_player->s.origin, vec3_origin, 3*waterlevel, 0, 0, MOD_LAVA); + } + + if (current_player->watertype & CONTENTS_SLIME) + { + if (!envirosuit) + { // no damage from slime with envirosuit + T_Damage (current_player, world, world, vec3_origin, current_player->s.origin, vec3_origin, 1*waterlevel, 0, 0, MOD_SLIME); + } + } + } +} + + +/* +=============== +G_SetClientEffects +=============== +*/ +void G_SetClientEffects (edict_t *ent) +{ + int pa_type; + int remaining; + + ent->s.effects = 0; + ent->s.renderfx = 0; + + if (ent->health <= 0 || level.intermissiontime) + return; + + if (ent->powerarmor_time > level.time) + { + pa_type = PowerArmorType (ent); + if (pa_type == POWER_ARMOR_SCREEN) + { + ent->s.effects |= EF_POWERSCREEN; + } + else if (pa_type == POWER_ARMOR_SHIELD) + { + ent->s.effects |= (EF_SPHERETRANS); +//ponko ent->s.effects |= EF_COLOR_SHELL; + ent->s.renderfx |= RF_SHELL_GREEN; + } + } + +//ZOID + CTFEffects(ent); +//ZOID + + if (ent->client->quad_framenum > level.framenum +//ZOID + && (level.framenum & 8) +//ZOID + ) + { + remaining = ent->client->quad_framenum - level.framenum; + if (remaining > 30 || (remaining & 4) ) + ent->s.effects |= EF_QUAD; + if (remaining == 30 && (ent->svflags & SVF_MONSTER)) // beginning to fade + gi.sound(ent, CHAN_ITEM, gi.soundindex("items/damage2.wav"), 1, ATTN_NORM, 0); + } + + // RAFAEL + if (ent->client->quadfire_framenum > level.framenum) + { + remaining = ent->client->quadfire_framenum - level.framenum; + if (remaining > 30 || (remaining & 4) ) + ent->s.effects |= EF_QUAD; + if (remaining == 30 && (ent->svflags & SVF_MONSTER)) // beginning to fade + gi.sound(ent, CHAN_ITEM, gi.soundindex("items/quadfire2.wav"), 1, ATTN_NORM, 0); + } + + if (ent->client->invincible_framenum > level.framenum +//ZOID + && (level.framenum & 8) +//ZOID + ) + { + remaining = ent->client->invincible_framenum - level.framenum; + if (remaining > 30 || (remaining & 4) ) + ent->s.effects |= EF_PENT; + if (remaining == 30 && (ent->svflags & SVF_MONSTER)) // beginning to fade + gi.sound(ent, CHAN_ITEM, gi.soundindex("items/protect2.wav"), 1, ATTN_NORM, 0); + } + + // show cheaters!!! + if (ent->flags & FL_GODMODE) + { + ent->s.effects |= EF_COLOR_SHELL; + ent->s.renderfx |= (RF_SHELL_RED|RF_SHELL_GREEN|RF_SHELL_BLUE); + } + +} + + +/* +=============== +G_SetClientEvent +=============== +*/ +void G_SetClientEvent (edict_t *ent) +{ + if (ent->s.event) + return; + + if ( ent->groundentity && xyspeed > 225) + { + if ( (int)(current_client->bobtime+bobmove) != bobcycle ) + { + ent->s.event = EV_FOOTSTEP; + PlayerNoise(ent, ent->s.origin, PNOISE_SELF); //ponko + } + } +} + +/* +=============== +G_SetClientSound +=============== +*/ +void G_SetClientSound (edict_t *ent) +{ + char *weap; + + if (ent->client->resp.game_helpchanged != game.helpchanged) + { + ent->client->resp.game_helpchanged = game.helpchanged; + ent->client->resp.helpchanged = 1; + } + + // help beep (no more than three times) + if (ent->client->resp.helpchanged && ent->client->resp.helpchanged <= 3 && !(level.framenum&63) ) + { + ent->client->resp.helpchanged++; + gi.sound (ent, CHAN_VOICE, gi.soundindex ("misc/pc_up.wav"), 1, ATTN_STATIC, 0); + } + + + if (ent->client->pers.weapon) + weap = ent->client->pers.weapon->classname; + else + weap = ""; + + if (ent->waterlevel && (ent->watertype&(CONTENTS_LAVA|CONTENTS_SLIME)) ) + ent->s.sound = snd_fry; + else if (strcmp(weap, "weapon_railgun") == 0) + ent->s.sound = gi.soundindex("weapons/rg_hum.wav"); + else if (strcmp(weap, "weapon_bfg") == 0) + ent->s.sound = gi.soundindex("weapons/bfg_hum.wav"); + else if (ent->client->weapon_sound) + ent->s.sound = ent->client->weapon_sound; + else + ent->s.sound = 0; +} + +/* +=============== +G_SetClientFrame +=============== +*/ +void G_SetClientFrame (edict_t *ent) +{ + gclient_t *client; + qboolean duck, run; + + if (ent->s.modelindex != 255) + return; // not in the player model + + client = ent->client; + + if (client->ps.pmove.pm_flags & PMF_DUCKED) + duck = true; + else + duck = false; + if (xyspeed) + run = true; + else + run = false; + + // check for stand/duck and stop/go transitions + if (duck != client->anim_duck && client->anim_priority < ANIM_DEATH) + goto newanim; + if (run != client->anim_run && client->anim_priority == ANIM_BASIC) + goto newanim; + if (!ent->groundentity && client->anim_priority <= ANIM_WAVE) + goto newanim; + + // ### Hentai ### BEGIN + if(client->anim_priority == ANIM_REVERSE) + { + if(ent->s.frame > client->anim_end) + { + ent->s.frame--; + return; + } + } + else if (ent->s.frame < client->anim_end) // ### Hentai ### END + { // continue an animation + ent->s.frame++; + return; + } + + if (client->anim_priority == ANIM_DEATH) + return; // stay there + if (client->anim_priority == ANIM_JUMP) + { + if (!ent->groundentity) + return; // stay there + ent->client->anim_priority = ANIM_WAVE; + ent->s.frame = FRAME_jump3; + ent->client->anim_end = FRAME_jump6; + return; + } + +newanim: + // return to either a running or standing frame + client->anim_priority = ANIM_BASIC; + client->anim_duck = duck; + client->anim_run = run; + + if (!ent->groundentity) + { +//ZOID: if on grapple, don't go into jump frame, go into standing +//frame + if (client->ctf_grapple) { + ent->s.frame = FRAME_stand01; + client->anim_end = FRAME_stand40; + } else { +//ZOID + client->anim_priority = ANIM_JUMP; + if (ent->s.frame != FRAME_jump2) + ent->s.frame = FRAME_jump1; + client->anim_end = FRAME_jump2; + } + } + else if (run) + { // running + if (duck) + { + ent->s.frame = FRAME_crwalk1; + client->anim_end = FRAME_crwalk6; + } + else + { + ent->s.frame = FRAME_run1; + client->anim_end = FRAME_run6; + } + } + else + { // standing + if (duck) + { + ent->s.frame = FRAME_crstnd01; + client->anim_end = FRAME_crstnd19; + } + else + { + ent->s.frame = FRAME_stand01; + client->anim_end = FRAME_stand40; + } + } +} + + +/* +================= +ClientEndServerFrame + +Called for each player at the end of the server frame +and right after spawning +================= +*/ +void ClientEndServerFrame (edict_t *ent) +{ + float bobtime; + int i; + + current_player = ent; + current_client = ent->client; + + // + // If the origin or velocity have changed since ClientThink(), + // update the pmove values. This will happen when the client + // is pushed by a bmodel or kicked by an explosion. + // + // If it wasn't updated here, the view position would lag a frame + // behind the body position when pushed -- "sinking into plats" + // + for (i=0 ; i<3 ; i++) + { + current_client->ps.pmove.origin[i] = ent->s.origin[i]*8.0; + current_client->ps.pmove.velocity[i] = ent->velocity[i]*8.0; + } + + // + // If the end of unit layout is displayed, don't give + // the player any normal movement attributes + // + if (level.intermissiontime) + { + // FIXME: add view drifting here? + current_client->ps.blend[3] = 0; + current_client->ps.fov = 90; + G_SetStats (ent); + return; + } + + AngleVectors (ent->client->v_angle, forward, right, up); + + // burn from lava, etc + P_WorldEffects (); + + // + // set model angles from view angles so other things in + // the world can tell which direction you are looking + // + if (ent->client->v_angle[PITCH] > 180) + ent->s.angles[PITCH] = (-360 + ent->client->v_angle[PITCH])/3; + else + ent->s.angles[PITCH] = ent->client->v_angle[PITCH]/3; + ent->s.angles[YAW] = ent->client->v_angle[YAW]; + ent->s.angles[ROLL] = 0; + ent->s.angles[ROLL] = SV_CalcRoll (ent->s.angles, ent->velocity)*4; + + // + // calculate speed and cycle to be used for + // all cyclic walking effects + // + xyspeed = sqrt(ent->velocity[0]*ent->velocity[0] + ent->velocity[1]*ent->velocity[1]); + + if (xyspeed < 5) + { + bobmove = 0; + current_client->bobtime = 0; // start at beginning of cycle again + } + else if (ent->groundentity) + { // so bobbing only cycles when on ground + if (xyspeed > 210) + bobmove = 0.25; + else if (xyspeed > 100) + bobmove = 0.125; + else + bobmove = 0.0625; + } + + bobtime = (current_client->bobtime += bobmove); + + if (current_client->ps.pmove.pm_flags & PMF_DUCKED) + bobtime *= 4; + + bobcycle = (int)bobtime; + bobfracsin = fabs(sin(bobtime*M_PI)); + + // detect hitting the floor + P_FallingDamage (ent); + + // apply all the damage taken this frame + P_DamageFeedback (ent); + + // determine the view offsets + SV_CalcViewOffset (ent); + + // determine the gun offsets + SV_CalcGunOffset (ent); + + // determine the full screen color blend + // must be after viewoffset, so eye contents can be + // accurately determined + // FIXME: with client prediction, the contents + // should be determined by the client + SV_CalcBlend (ent); + + // chase cam stuff + if (ent->client->resp.spectator) + G_SetSpectatorStats(ent); + else + G_SetStats (ent); + + G_CheckChaseStats(ent); + + G_SetClientEvent (ent); + + G_SetClientEffects (ent); + + G_SetClientSound (ent); + + G_SetClientFrame (ent); + + VectorCopy (ent->velocity, ent->client->oldvelocity); + VectorCopy (ent->client->ps.viewangles, ent->client->oldviewangles); + + // clear weapon kicks + VectorClear (ent->client->kick_origin); + VectorClear (ent->client->kick_angles); + + // if the scoreboard is up, update it + if (ent->client->showscores && !(level.framenum & 31) + && !(ent->svflags & SVF_MONSTER)) + { +//ZOID + if (ent->client->menu) { + PMenu_Update(ent); + } else +//ZOID + DeathmatchScoreboardMessage (ent, ent->enemy); + gi.unicast (ent, false); + } +} + + +//------------------------------------------------------------------------- + + +// BOT AREA + +/* +============= +B_WorldEffects +============= +*/ +void B_WorldEffects (edict_t *ent) +{ + qboolean breather; + qboolean envirosuit; + int waterlevel, old_waterlevel; + gclient_t *client; + + client = ent->client; + + if (ent->movetype == MOVETYPE_NOCLIP) + { + ent->air_finished = level.time + 12; // don't need air + return; + } + + waterlevel = ent->waterlevel; + old_waterlevel = client->old_waterlevel; + client->old_waterlevel = waterlevel; + + breather = client->breather_framenum > level.framenum; + envirosuit = client->enviro_framenum > level.framenum; + + // + // if just entered a water volume, play a sound + // + if (!old_waterlevel && waterlevel) + { + PlayerNoise(current_player, current_player->s.origin, PNOISE_SELF); + if (ent->watertype & CONTENTS_LAVA) + gi.sound (current_player, CHAN_BODY, gi.soundindex("player/lava_in.wav"), 1, ATTN_NORM, 0); + else if (ent->watertype & CONTENTS_SLIME) + gi.sound (ent, CHAN_BODY, gi.soundindex("player/watr_in.wav"), 1, ATTN_NORM, 0); + else if (ent->watertype & CONTENTS_WATER) + gi.sound (current_player, CHAN_BODY, gi.soundindex("player/watr_in.wav"), 1, ATTN_NORM, 0); + ent->flags |= FL_INWATER; + + // clear damage_debounce, so the pain sound will play immediately + ent->damage_debounce_time = level.time - 1; + } + + // + // if just completely exited a water volume, play a sound + // + if (old_waterlevel && ! waterlevel) + { + PlayerNoise(current_player, current_player->s.origin, PNOISE_SELF); + gi.sound (current_player, CHAN_BODY, gi.soundindex("player/watr_out.wav"), 1, ATTN_NORM, 0); + current_player->flags &= ~FL_INWATER; + } + + // + // check for head just going under water + // + if (old_waterlevel != 3 && waterlevel == 3) + { + gi.sound (current_player, CHAN_BODY, gi.soundindex("player/watr_un.wav"), 1, ATTN_NORM, 0); + } + + // + // check for head just coming out of water + // + if (old_waterlevel == 3 && waterlevel != 3) + { + if (current_player->air_finished < level.time) + { // gasp for air + gi.sound (current_player, CHAN_VOICE, gi.soundindex("player/gasp1.wav"), 1, ATTN_NORM, 0); + PlayerNoise(current_player, current_player->s.origin, PNOISE_SELF); + } + else if (current_player->air_finished < level.time + 11) + { // just break surface + gi.sound (current_player, CHAN_VOICE, gi.soundindex("player/gasp2.wav"), 1, ATTN_NORM, 0); + } + } + + // + // check for drowning + // + if (waterlevel == 3) + { + // breather or envirosuit give air + if (breather || envirosuit) + { + current_player->air_finished = level.time + 10; + + if (((int)(current_client->breather_framenum - level.framenum) % 25) == 0) + { + if (!current_client->breather_sound) + gi.sound (current_player, CHAN_AUTO, gi.soundindex("player/u_breath1.wav"), 1, ATTN_NORM, 0); + else + gi.sound (current_player, CHAN_AUTO, gi.soundindex("player/u_breath2.wav"), 1, ATTN_NORM, 0); + current_client->breather_sound ^= 1; + PlayerNoise(current_player, current_player->s.origin, PNOISE_SELF); + //FIXME: release a bubble? + } + } + + // if out of air, start drowning + if (current_player->air_finished < level.time) + { // drown! + if (current_player->client->next_drown_time < level.time + && current_player->health > 0) + { + current_player->client->next_drown_time = level.time + 1; + + // take more damage the longer underwater + current_player->dmg += 2; + if (current_player->dmg > 15) + current_player->dmg = 15; + + // play a gurp sound instead of a normal pain sound + if (current_player->health <= current_player->dmg) + gi.sound (current_player, CHAN_VOICE, gi.soundindex("player/drown1.wav"), 1, ATTN_NORM, 0); + else if (rand()&1) + gi.sound (current_player, CHAN_VOICE, gi.soundindex("*gurp1.wav"), 1, ATTN_NORM, 0); + else + gi.sound (current_player, CHAN_VOICE, gi.soundindex("*gurp2.wav"), 1, ATTN_NORM, 0); + + current_player->pain_debounce_time = level.time; + + T_Damage (current_player, world, world, vec3_origin, current_player->s.origin, vec3_origin, current_player->dmg, 0, DAMAGE_NO_ARMOR, MOD_WATER); + } + } + } + else + { + current_player->air_finished = level.time + 12; + current_player->dmg = 2; + } + + // + // check for sizzle damage + // + if (waterlevel && (current_player->watertype&(CONTENTS_LAVA|CONTENTS_SLIME)) ) + { + if (current_player->watertype & CONTENTS_LAVA) + { + if (current_player->health > 0 + && current_player->pain_debounce_time <= level.time + && current_client->invincible_framenum < level.framenum) + { + if (rand()&1) + gi.sound (current_player, CHAN_VOICE, gi.soundindex("player/burn1.wav"), 1, ATTN_NORM, 0); + else + gi.sound (current_player, CHAN_VOICE, gi.soundindex("player/burn2.wav"), 1, ATTN_NORM, 0); + current_player->pain_debounce_time = level.time + 1; + } + + if (envirosuit) // take 1/3 damage with envirosuit + T_Damage (current_player, world, world, vec3_origin, current_player->s.origin, vec3_origin, 1*waterlevel, 0, 0, MOD_LAVA); + else + T_Damage (current_player, world, world, vec3_origin, current_player->s.origin, vec3_origin, 3*waterlevel, 0, 0, MOD_LAVA); + } + + if (current_player->watertype & CONTENTS_SLIME) + { + if (!envirosuit) + { // no damage from slime with envirosuit + T_Damage (current_player, world, world, vec3_origin, current_player->s.origin, vec3_origin, 1*waterlevel, 0, 0, MOD_SLIME); + } + } + } +} + + + + + +/* +=============== +B_DamageFeedback + +Handles color blends and view kicks +=============== +*/ +void B_DamageFeedback (edict_t *player) +{ + gclient_t *client; + float realcount, count; + int r, l; + + client = player->client; + + if(player->deadflag) return; + + // total points of damage shot at the player this frame + count = (client->damage_blood + client->damage_armor + client->damage_parmor); + if (count == 0) + return; // didn't take any damage + + // start a pain animation if still in the player model + if (client->anim_priority < ANIM_PAIN && player->s.modelindex == 255) + { + static int i; + client->anim_priority = ANIM_PAIN; + if (client->ps.pmove.pm_flags & PMF_DUCKED) + { + player->s.frame = FRAME_crpain1-1; + client->anim_end = FRAME_crpain4; + } + else + { + i = (i+1)%3; + switch (i) + { + case 0: + player->s.frame = FRAME_pain101-1; + client->anim_end = FRAME_pain104; + break; + case 1: + player->s.frame = FRAME_pain201-1; + client->anim_end = FRAME_pain204; + break; + case 2: + player->s.frame = FRAME_pain301-1; + client->anim_end = FRAME_pain304; + break; + } + } + } + + realcount = count; + if (count < 10) + count = 10; // always make a visible effect + + // play an apropriate pain sound + if ((level.time > player->pain_debounce_time) && !(player->flags & FL_GODMODE) && (client->invincible_framenum <= level.framenum)) + { + r = 1 + (rand()&1); + player->pain_debounce_time = level.time + 0.7; + if (player->health < 25) + l = 25; + else if (player->health < 50) + l = 50; + else if (player->health < 75) + l = 75; + else + l = 100; + gi.sound (player, CHAN_VOICE, gi.soundindex(va("*pain%i_%i.wav", l, r)), 1, ATTN_NORM, 0); + } + // + // clear totals + // + client->damage_blood = 0; + client->damage_armor = 0; + client->damage_parmor = 0; + client->damage_knockback = 0; +} + + +/* +================= +BotEndServerFrame + +Called for each player at the end of the server frame +and right after spawning +================= +*/ +void BotEndServerFrame (edict_t *ent) +{ + float bobtime; + vec3_t v; + + current_player = ent; + current_client = ent->client; + + // + // If the origin or velocity have changed since ClientThink(), + // update the pmove values. This will happen when the client + // is pushed by a bmodel or kicked by an explosion. + // + // If it wasn't updated here, the view position would lag a frame + // behind the body position when pushed -- "sinking into plats" + // +// for (i=0 ; i<3 ; i++) +// { +// current_client->ps.pmove.origin[i] = ent->s.origin[i]*8.0; +// current_client->ps.pmove.velocity[i] = ent->velocity[i]*8.0; +// } + + // + // If the end of unit layout is displayed, don't give + // the player any normal movement attributes + // + if (level.intermissiontime) + { + // FIXME: add view drifting here? +// current_client->ps.blend[3] = 0; + current_client->ps.fov = 90; + G_SetStats (ent); + return; + } + + AngleVectors (ent->client->v_angle, forward, right, up); + + + + // burn from lava, etc + B_WorldEffects (ent); + + // + // set model angles from view angles so other things in + // the world can tell which direction you are looking + // +/* if (ent->client->v_angle[PITCH] > 180) + ent->s.angles[PITCH] = (-360 + ent->client->v_angle[PITCH])/3; + else + ent->s.angles[PITCH] = ent->client->v_angle[PITCH]/3; + ent->s.angles[YAW] = ent->client->v_angle[YAW]; + ent->s.angles[ROLL] = 0SV_CalcRoll (ent->s.angles, ent->velocity)*4; +*/ + ent->s.angles[ROLL] = 0; + // + // calculate speed and cycle to be used for + // all cyclic walking effects + // +// xyspeed = sqrt(ent->velocity[0]*ent->velocity[0] + ent->velocity[1]*ent->velocity[1]); + VectorSubtract(ent->s.origin,ent->s.old_origin,v); + v[2] = 0; + xyspeed = VectorLength(v) * 10; + + + if (xyspeed < 5) + { + bobmove = 0; + current_client->bobtime = 0; // start at beginning of cycle again + } + else if (ent->groundentity) + { // so bobbing only cycles when on ground + if (xyspeed > 210) + bobmove = 0.25; + else if (xyspeed > 100) + bobmove = 0.125; + else + bobmove = 0.0625; + } + + bobtime = (current_client->bobtime += bobmove); + + if (current_client->ps.pmove.pm_flags & PMF_DUCKED) + bobtime *= 4; + + bobcycle = (int)bobtime; + bobfracsin = fabs(sin(bobtime*M_PI)); + + // detect hitting the floor + P_FallingDamage (ent); + + // apply all the damage taken this frame + B_DamageFeedback (ent); + + + if(!ent->deadflag) + { + // determine the view offsets + SV_CalcViewOffset (ent); + + // determine the gun offsets + SV_CalcGunOffset (ent); + + // determine the full screen color blend + // must be after viewoffset, so eye contents can be + // accurately determined + // FIXME: with client prediction, the contents + // should be determined by the client +// SV_CalcBlend (ent); + + G_SetClientEvent (ent); + + G_SetClientEffects (ent); + + G_SetClientSound (ent); + + } + + if(ent->deadflag) + { + G_SetClientEffects (ent); + ent->client->anim_priority = ANIM_DEATH; + if(ent->s.modelindex != skullindex && ent->s.modelindex != headindex) + { + if(ent->s.frame < ent->client->anim_end) G_SetClientFrame (ent); + } + else ent->s.frame = 0; + } + else G_SetClientFrame (ent); + + VectorCopy (ent->velocity, ent->client->oldvelocity); +// VectorCopy (ent->client->ps.viewangles, ent->client->oldviewangles); + + // clear weapon kicks + VectorClear (ent->client->kick_origin); + VectorClear (ent->client->kick_angles); + + // if the scoreboard is up, update it +/* if (ent->client->showscores && !(level.framenum & 31) ) + { +//ZOID + if (ent->client->menu) { + PMenu_Update(ent); + } else +//ZOID + DeathmatchScoreboardMessage (ent, ent->enemy); + gi.unicast (ent, false); + }*/ +} + diff --git a/src/p_weapon.c b/src/p_weapon.c new file mode 100644 index 0000000..07ba2bf --- /dev/null +++ b/src/p_weapon.c @@ -0,0 +1,2368 @@ +// g_weapon.c + +#include "g_local.h" +#include "m_player.h" +#include "bot.h" + +static qboolean is_quad; +// RAFAEL +static qboolean is_quadfire; +static byte is_silenced; + + +void weapon_grenade_fire (edict_t *ent, qboolean held); +// RAFAEL +void weapon_trap_fire (edict_t *ent, qboolean held); + +void P_ProjectSource (gclient_t *client, vec3_t point, vec3_t distance, vec3_t forward, vec3_t right, vec3_t result) +{ + vec3_t _distance; + + VectorCopy (distance, _distance); + if (client->pers.hand == LEFT_HANDED) + _distance[1] *= -1; + else if (client->pers.hand == CENTER_HANDED) + _distance[1] = 0; + G_ProjectSource (point, _distance, forward, right, result); +} + + +/* +=============== +PlayerNoise + +Each player can have two noise objects associated with it: +a personal noise (jumping, pain, weapon firing), and a weapon +target noise (bullet wall impacts) + +Monsters that don't directly see the player can move +to a noise in hopes of seeing the player from there. +=============== +*/ +void PlayerNoise(edict_t *who, vec3_t where, int type) +{ + edict_t *noise; + + if (type == PNOISE_WEAPON) + { + if (who->client->silencer_shots) + { + who->client->silencer_shots--; + return; + } + } + + if (deathmatch->value) + return; + + if (who->flags & FL_NOTARGET) + return; + + + if (!who->mynoise) + { + noise = G_Spawn(); + noise->classname = "player_noise"; + VectorSet (noise->mins, -8, -8, -8); + VectorSet (noise->maxs, 8, 8, 8); + noise->owner = who; + noise->svflags = SVF_NOCLIENT; + who->mynoise = noise; + + noise = G_Spawn(); + noise->classname = "player_noise"; + VectorSet (noise->mins, -8, -8, -8); + VectorSet (noise->maxs, 8, 8, 8); + noise->owner = who; + noise->svflags = SVF_NOCLIENT; + who->mynoise2 = noise; + } + + if (type == PNOISE_SELF || type == PNOISE_WEAPON) + { + noise = who->mynoise; + level.sound_entity = noise; + level.sound_entity_framenum = level.framenum; + } + else // type == PNOISE_IMPACT + { + noise = who->mynoise2; + level.sound2_entity = noise; + level.sound2_entity_framenum = level.framenum; + } + + VectorCopy (where, noise->s.origin); + VectorSubtract (where, noise->maxs, noise->absmin); + VectorAdd (where, noise->maxs, noise->absmax); + noise->teleport_time = level.time; + gi.linkentity (noise); +} + +void ShowGun(edict_t *ent); +qboolean Pickup_Weapon (edict_t *ent, edict_t *other) +{ + int index,i; + gitem_t *ammo,*item; + + index = ITEM_INDEX(ent->item); + + if ( ( ((int)(dmflags->value) & DF_WEAPONS_STAY) || coop->value) + && other->client->pers.inventory[index]) + { + if (!(ent->spawnflags & (DROPPED_ITEM | DROPPED_PLAYER_ITEM) ) ) + return false; // leave the weapon for others to pickup + } + + other->client->pers.inventory[index]++; + + if (!(ent->spawnflags & DROPPED_ITEM) ) + { + // give them some ammo with it + ammo = FindItem (ent->item->ammo); + + if ( (int)dmflags->value & DF_INFINITE_AMMO ) + Add_Ammo (other, ammo, 1000); + else + Add_Ammo (other, ammo, ammo->quantity); + + if (! (ent->spawnflags & DROPPED_PLAYER_ITEM) ) + { + if (deathmatch->value) + { + if ((int)(dmflags->value) & DF_WEAPONS_STAY) + ent->flags |= FL_RESPAWN; + else + SetRespawn (ent, 30); + } + if (coop->value) + ent->flags |= FL_RESPAWN; + } + } + + if((other->svflags & SVF_MONSTER) && ctf->value && other->client->zc.route_trace) + { + if(!other->client->zc.first_target) + { + for(i = 0;i < (5 * 2);i++) + { + if((other->client->zc.routeindex + i) >= CurrentIndex) break; + if(Route[other->client->zc.routeindex + i].state == GRS_GRAPSHOT) + { + item = Fdi_GRAPPLE;//FindItem("Grapple"); + if( other->client->pers.inventory[ITEM_INDEX(item)]) item->use(other,item); + return true; + } + } + } + } + + if (other->client->pers.weapon != ent->item && + (other->client->pers.inventory[index] == 1) && + ( !deathmatch->value || other->client->pers.weapon == Fdi_BLASTER/*FindItem("blaster")*/ ) ) + { + if(other->svflags & SVF_MONSTER) ent->item->use(other,ent->item); + else other->client->newweapon = ent->item; + } + + if(other->svflags & SVF_MONSTER + && (other->client->pers.weapon == Fdi_BLASTER/*FindItem("blaster") */ + || other->client->pers.weapon == Fdi_GRENADES/*FindItem("Grenades")*/)) + { + ent->item->use(other,ent->item);//other->client->pers.weapon = ent->item; + ShowGun(other); + } + return true; +} + + +/* +=============== +ChangeWeapon + +The old weapon has been dropped all the way, so make the new one +current +=============== +*/ +// ### Hentai ### BEGIN +void ShowGun(edict_t *ent) +{ + int i,j; + + if(!ent->client->pers.weapon) + { + ent->s.modelindex2 = 0; + return; + } + if(!vwep->value) + { + ent->s.modelindex2 = 255; + return; + } + + j = Get_KindWeapon(ent->client->pers.weapon); + if(j == WEAP_GRAPPLE) j = WEAP_BLASTER; + + ent->s.modelindex2 = 255; + if (ent->client->pers.weapon) + i = ((j & 0xff) << 8); + else + i = 0; + + ent->s.skinnum = (ent - g_edicts - 1) | i; + +} +// ### Hentai ### END + +void ChangeWeapon (edict_t *ent) +{ + char *mdl; + + if (ent->client->grenade_time) + { + ent->client->grenade_time = level.time; + ent->client->weapon_sound = 0; + weapon_grenade_fire (ent, false); + ent->client->grenade_time = 0; + } + + ent->client->pers.lastweapon = ent->client->pers.weapon; + ent->client->pers.weapon = ent->client->newweapon; + ent->client->newweapon = NULL; + ent->client->machinegun_shots = 0; + + if (ent->client->pers.weapon && ent->client->pers.weapon->ammo) + ent->client->ammo_index = ITEM_INDEX(FindItem(ent->client->pers.weapon->ammo)); + else + ent->client->ammo_index = 0; + + if (!ent->client->pers.weapon) + { // dead + ent->client->ps.gunindex = 0; + return; + } + + ent->client->weaponstate = WEAPON_ACTIVATING; + ent->client->ps.gunframe = 0; +//lm ctf + mdl = ent->client->pers.weapon->view_model; + if(ctf->value == 2) + { + if(Q_stricmp (ent->client->pers.weapon->classname, "weapon_grapple") == 0) + { + mdl = "models/weapons/v_hook/tris.md2"; + } + + } + ent->client->ps.gunindex = gi.modelindex(mdl/*ent->client->pers.weapon->view_model*/); +//lm ctf + + // ### Hentai ### BEGIN + ent->client->anim_priority = ANIM_PAIN; + if(ent->client->ps.pmove.pm_flags & PMF_DUCKED) + { + ent->s.frame = FRAME_crpain1; + ent->client->anim_end = FRAME_crpain4; + } + else + { + ent->s.frame = FRAME_pain301; + ent->client->anim_end = FRAME_pain304; + + } + + ShowGun(ent); + + // ### Hentai ### END +} + +/* +================= +NoAmmoWeaponChange +================= +*/ +void NoAmmoWeaponChange (edict_t *ent) +{ + gitem_t* item = NULL; + + if ( ent->client->pers.inventory[ITEM_INDEX(Fdi_SLUGS/*FindItem("slugs")*/)] + && ent->client->pers.inventory[ITEM_INDEX(Fdi_RAILGUN/*FindItem("railgun")*/)] ) + { + item = Fdi_RAILGUN;//FindItem ("railgun"); +// return; + } + // RAFAEL + else if ( ent->client->pers.inventory[ITEM_INDEX (Fdi_MAGSLUGS/*FindItem ("mag slug")*/)] + && ent->client->pers.inventory[ITEM_INDEX (Fdi_PHALANX/*FindItem ("phalanx")*/)]) + { + item = Fdi_PHALANX;//FindItem ("phalanx"); + } + // RAFAEL + else if ( ent->client->pers.inventory[ITEM_INDEX (Fdi_CELLS/*FindItem ("cells")*/)] + && ent->client->pers.inventory[ITEM_INDEX (Fdi_BOOMER/*FindItem ("ionripper")*/)]) + { + item = Fdi_BOOMER;//FindItem ("ionrippergun"); + } + + else if ( ent->client->pers.inventory[ITEM_INDEX(Fdi_CELLS/*FindItem("cells")*/)] + && ent->client->pers.inventory[ITEM_INDEX(Fdi_HYPERBLASTER/*FindItem("hyperblaster")*/)] ) + { + item = Fdi_HYPERBLASTER;//FindItem ("hyperblaster"); +// return; + } + else if ( ent->client->pers.inventory[ITEM_INDEX(Fdi_BULLETS/*FindItem("bullets")*/)] + && ent->client->pers.inventory[ITEM_INDEX(Fdi_CHAINGUN/*FindItem("chaingun")*/)] ) + { + item = Fdi_CHAINGUN;//FindItem ("chaingun"); +// return; + } + else if ( ent->client->pers.inventory[ITEM_INDEX(Fdi_BULLETS/*FindItem("bullets")*/)] + && ent->client->pers.inventory[ITEM_INDEX(Fdi_MACHINEGUN/*FindItem("machinegun")*/)] ) + { + item = Fdi_MACHINEGUN;//FindItem ("machinegun"); +// return; + } + else if ( ent->client->pers.inventory[ITEM_INDEX(Fdi_SHELLS/*FindItem("shells")*/)] > 1 + && ent->client->pers.inventory[ITEM_INDEX(Fdi_SUPERSHOTGUN/*FindItem("super shotgun")*/)] ) + { + item = Fdi_SUPERSHOTGUN;//FindItem ("super shotgun"); +// return; + } + else if ( ent->client->pers.inventory[ITEM_INDEX(Fdi_SHELLS/*FindItem("shells")*/)] + && ent->client->pers.inventory[ITEM_INDEX(Fdi_SHOTGUN/*FindItem("shotgun")*/)] ) + { + item = Fdi_SHOTGUN;//FindItem ("shotgun"); +// return; + } + if(item == NULL) item = Fdi_BLASTER;//FindItem ("blaster"); + + if(ent->svflags & SVF_MONSTER) item->use(ent,item); + else ent->client->newweapon = item; + +} + +/* +================= +Think_Weapon + +Called by ClientBeginServerFrame and ClientThink +================= +*/ +void Think_Weapon (edict_t *ent) +{ + // if just died, put the weapon away + if (ent->health < 1) + { + ent->client->newweapon = NULL; + ChangeWeapon (ent); + } + + // call active weapon think routine + if (ent->client->pers.weapon && ent->client->pers.weapon->weaponthink) + { + is_quad = (ent->client->quad_framenum > level.framenum); + // RAFAEL + is_quadfire = (ent->client->quadfire_framenum > level.framenum); + if (ent->client->silencer_shots) + is_silenced = MZ_SILENCED; + else + is_silenced = 0; + ent->client->pers.weapon->weaponthink (ent); + } +} + + +/* +================ +Use_Weapon + +Make the weapon ready if there is ammo +================ +*/ +void Use_Weapon (edict_t *ent, gitem_t *item) +{ + int ammo_index; + gitem_t *ammo_item; + + // see if we're already using it + if (item == ent->client->pers.weapon) + return; + + if(ent->svflags & SVF_MONSTER) + { + if(ent->client->newweapon != NULL) return; + if(!Q_stricmp (item->pickup_name, "Blaster")) + { + ent->client->newweapon = item; + return; + } + } + + if (item->ammo && !g_select_empty->value && !(item->flags & IT_AMMO)) + { + ammo_item = FindItem(item->ammo); + ammo_index = ITEM_INDEX(ammo_item); + + if (!ent->client->pers.inventory[ammo_index]) + { + if(!(ent->svflags & SVF_MONSTER)) gi.cprintf (ent, PRINT_HIGH, "No %s for %s.\n", ammo_item->pickup_name, item->pickup_name); + return; + } + + if (ent->client->pers.inventory[ammo_index] < item->quantity) + { + if(!(ent->svflags & SVF_MONSTER)) gi.cprintf (ent, PRINT_HIGH, "Not enough %s for %s.\n", ammo_item->pickup_name, item->pickup_name); + return; + } + } + + // change to this weapon when down + ent->client->newweapon = item; +} + +// RAFAEL 14-APR-98 +void Use_Weapon2 (edict_t *ent, gitem_t *item) +{ + int ammo_index; + gitem_t *ammo_item; + gitem_t *nextitem; + int index; + + if(ent->svflags & SVF_MONSTER) + { + Use_Weapon(ent,item); + return; + } + + if (strcmp (item->pickup_name, "HyperBlaster") == 0) + { + if (item == ent->client->pers.weapon) + { + item = Fdi_BOOMER;//FindItem ("Ionripper"); + index = ITEM_INDEX (item); + if (!ent->client->pers.inventory[index]) + { + item = Fdi_HYPERBLASTER;//FindItem ("HyperBlaster"); + } + } + } + + else if (strcmp (item->pickup_name, "Railgun") == 0) + { + ammo_item = FindItem(item->ammo); + ammo_index = ITEM_INDEX(ammo_item); + if (!ent->client->pers.inventory[ammo_index]) + { + nextitem = Fdi_PHALANX;//FindItem ("Phalanx"); + ammo_item = FindItem(nextitem->ammo); + ammo_index = ITEM_INDEX(ammo_item); + if (ent->client->pers.inventory[ammo_index]) + { + item = Fdi_PHALANX;//FindItem ("Phalanx"); + index = ITEM_INDEX (item); + if (!ent->client->pers.inventory[index]) + { + item = Fdi_RAILGUN;//FindItem ("Railgun"); + } + } + } + else if (item == ent->client->pers.weapon) + { + item = Fdi_PHALANX;//FindItem ("Phalanx"); + index = ITEM_INDEX (item); + if (!ent->client->pers.inventory[index]) + { + item = Fdi_RAILGUN;//FindItem ("Railgun"); + } + } + + } + + // see if we're already using it + if (item == ent->client->pers.weapon) + return; + + if (item->ammo) + { + ammo_item = FindItem(item->ammo); + ammo_index = ITEM_INDEX(ammo_item); + if (!ent->client->pers.inventory[ammo_index] && !g_select_empty->value) + { + gi.cprintf (ent, PRINT_HIGH, "No %s for %s.\n", ammo_item->pickup_name, item->pickup_name); + return; + } + } + + // change to this weapon when down + ent->client->newweapon = item; + +} +// END 14-APR-98 + +/* +================ +Drop_Weapon +================ +*/ +void Drop_Weapon (edict_t *ent, gitem_t *item) +{ + int index; + + if ((int)(dmflags->value) & DF_WEAPONS_STAY) + return; + + index = ITEM_INDEX(item); + // see if we're already using it + if ( ((item == ent->client->pers.weapon) || (item == ent->client->newweapon))&& (ent->client->pers.inventory[index] == 1) ) + { + if(!(ent->svflags & SVF_MONSTER)) gi.cprintf (ent, PRINT_HIGH, "Can't drop current weapon\n"); + return; + } + + Drop_Item (ent, item); + ent->client->pers.inventory[index]--; +} + + +/* +================ +Weapon_Generic + +A generic function to handle the basics of weapon thinking +================ +*/ +#define FRAME_FIRE_FIRST (FRAME_ACTIVATE_LAST + 1) +#define FRAME_IDLE_FIRST (FRAME_FIRE_LAST + 1) +#define FRAME_DEACTIVATE_FIRST (FRAME_IDLE_LAST + 1) + +void Weapon_Generic2 (edict_t *ent, int FRAME_ACTIVATE_LAST, int FRAME_FIRE_LAST, int FRAME_IDLE_LAST, int FRAME_DEACTIVATE_LAST, int *pause_frames, int *fire_frames, void (*fire)(edict_t *ent)) +{ + int n; + + if (ent->client->weaponstate == WEAPON_DROPPING) + { + if (ent->client->ps.gunframe == FRAME_DEACTIVATE_LAST) + { + ChangeWeapon (ent); + return; + }// ### Hentai ### BEGIN + else if((FRAME_DEACTIVATE_LAST - ent->client->ps.gunframe) == 4) + { + ent->client->anim_priority = ANIM_REVERSE; + if(ent->client->ps.pmove.pm_flags & PMF_DUCKED) + { + ent->s.frame = FRAME_crpain4+1; + ent->client->anim_end = FRAME_crpain1; + } + else + { + ent->s.frame = FRAME_pain304+1; + ent->client->anim_end = FRAME_pain301; + + } + } + // ### Hentai ### END + + ent->client->ps.gunframe++; + return; + } + + if (ent->client->weaponstate == WEAPON_ACTIVATING) + { + if (ent->client->ps.gunframe == FRAME_ACTIVATE_LAST) + { + ent->client->weaponstate = WEAPON_READY; + ent->client->ps.gunframe = FRAME_IDLE_FIRST; + return; + } + + ent->client->ps.gunframe++; + return; + } + + if ((ent->client->newweapon) && (ent->client->weaponstate != WEAPON_FIRING)) + { + ent->client->weaponstate = WEAPON_DROPPING; + ent->client->ps.gunframe = FRAME_DEACTIVATE_FIRST; + // ### Hentai ### BEGIN + if((FRAME_DEACTIVATE_LAST - FRAME_DEACTIVATE_FIRST) < 4) + { + ent->client->anim_priority = ANIM_REVERSE; + if(ent->client->ps.pmove.pm_flags & PMF_DUCKED) + { + ent->s.frame = FRAME_crpain4+1; + ent->client->anim_end = FRAME_crpain1; + } + else + { + ent->s.frame = FRAME_pain304+1; + ent->client->anim_end = FRAME_pain301; + + } + } + // ### Hentai ### END + + return; + } + + if (ent->client->weaponstate == WEAPON_READY) + { + if ( ((ent->client->latched_buttons|ent->client->buttons) & BUTTON_ATTACK) ) + { + ent->client->latched_buttons &= ~BUTTON_ATTACK; + if ((!ent->client->ammo_index) || + ( ent->client->pers.inventory[ent->client->ammo_index] >= ent->client->pers.weapon->quantity)) + { + ent->client->ps.gunframe = FRAME_FIRE_FIRST; + ent->client->weaponstate = WEAPON_FIRING; + + // start the animation + ent->client->anim_priority = ANIM_ATTACK; + if (ent->client->ps.pmove.pm_flags & PMF_DUCKED) + { + ent->s.frame = FRAME_crattak1-1; + ent->client->anim_end = FRAME_crattak9; + } + else + { + ent->s.frame = FRAME_attack1-1; + ent->client->anim_end = FRAME_attack8; + } + } + else + { + if (level.time >= ent->pain_debounce_time) + { + gi.sound(ent, CHAN_VOICE, gi.soundindex("weapons/noammo.wav"), 1, ATTN_NORM, 0); + ent->pain_debounce_time = level.time + 1; + } +/*if(ent->client->pers.weapon) gi.bprintf(PRINT_HIGH,"weapon %s %i\n" + ,ent->client->pers.weapon->pickup_name + ,ent->client->ammo_index);*/ +// ,ent->client->pers.inventory[ITEM_INDEX(FindItem(ent->client->pers.weapon->ammo))] +// ); + NoAmmoWeaponChange (ent); + } + } + else + { + if (ent->client->ps.gunframe == FRAME_IDLE_LAST) + { + ent->client->ps.gunframe = FRAME_IDLE_FIRST; + return; + } + + if (pause_frames) + { + for (n = 0; pause_frames[n]; n++) + { + if (ent->client->ps.gunframe == pause_frames[n]) + { + if (rand()&15) + return; + } + } + } + + ent->client->ps.gunframe++; + return; + } + } + + if (ent->client->weaponstate == WEAPON_FIRING) + { + for (n = 0; fire_frames[n]; n++) + { + if (ent->client->ps.gunframe == fire_frames[n]) + { +//ZOID + if (!CTFApplyStrengthSound(ent)) +//ZOID + if (ent->client->quad_framenum > level.framenum) + gi.sound(ent, CHAN_ITEM, gi.soundindex("items/damage3.wav"), 1, ATTN_NORM, 0); +//ZOID + CTFApplyHasteSound(ent); +//ZOID + fire (ent); + break; + } + } + + if (!fire_frames[n]) + ent->client->ps.gunframe++; + + if (ent->client->ps.gunframe == FRAME_IDLE_FIRST+1) + ent->client->weaponstate = WEAPON_READY; + } +} + +//ZOID +void Weapon_Generic (edict_t *ent, int FRAME_ACTIVATE_LAST, int FRAME_FIRE_LAST, int FRAME_IDLE_LAST, int FRAME_DEACTIVATE_LAST, int *pause_frames, int *fire_frames, void (*fire)(edict_t *ent)) +{ + int oldstate = ent->client->weaponstate; + + Weapon_Generic2 (ent, FRAME_ACTIVATE_LAST, FRAME_FIRE_LAST, + FRAME_IDLE_LAST, FRAME_DEACTIVATE_LAST, pause_frames, + fire_frames, fire); + + // run the weapon frame again if hasted + if (stricmp(ent->client->pers.weapon->pickup_name, "Grapple") == 0 && + ent->client->weaponstate == WEAPON_FIRING) + return; + + if ((CTFApplyHaste(ent) || + (Q_stricmp(ent->client->pers.weapon->pickup_name, "Grapple") == 0 && + ent->client->weaponstate != WEAPON_FIRING)) + && oldstate == ent->client->weaponstate) { + Weapon_Generic2 (ent, FRAME_ACTIVATE_LAST, FRAME_FIRE_LAST, + FRAME_IDLE_LAST, FRAME_DEACTIVATE_LAST, pause_frames, + fire_frames, fire); + } +} +//ZOID + +/* +====================================================================== + +GRENADE + +====================================================================== +*/ + +#define GRENADE_TIMER 3.0 +#define GRENADE_MINSPEED 400 +#define GRENADE_MAXSPEED 800 + +void weapon_grenade_fire (edict_t *ent, qboolean held) +{ + vec3_t offset; + vec3_t forward, right; + vec3_t start; + int damage = 125; + float timer; + int speed; + float radius; + + radius = damage+40; + if (is_quad) + damage *= 4; + + VectorSet(offset, 8, 8, ent->viewheight-8); + AngleVectors (ent->client->v_angle, forward, right, NULL); + P_ProjectSource (ent->client, ent->s.origin, offset, forward, right, start); + + timer = ent->client->grenade_time - level.time; + speed = GRENADE_MINSPEED + (GRENADE_TIMER - timer) * ((GRENADE_MAXSPEED - GRENADE_MINSPEED) / GRENADE_TIMER); + fire_grenade2 (ent, start, forward, damage, speed, timer, radius, held); + + // ### Hentai ### BEGIN + +/* if (ent->client->ps.pmove.pm_flags & PMF_DUCKED) + { + ent->client->anim_priority = ANIM_ATTACK; + ent->s.frame = FRAME_crattak1-1; + ent->client->anim_end = FRAME_crattak3; + } + else + { + ent->client->anim_priority = ANIM_REVERSE; + ent->s.frame = FRAME_wave08; + ent->client->anim_end = FRAME_wave01; + }*/ + // ### Hentai ### END + + if (! ( (int)dmflags->value & DF_INFINITE_AMMO ) ) + ent->client->pers.inventory[ent->client->ammo_index]--; + + ent->client->grenade_time = level.time + 1.0; + + if(ent->deadflag || ent->s.modelindex != 255) // VWep animations screw up corpses + { + return; + } + + if (ent->health <= 0) + return; + + if (ent->client->ps.pmove.pm_flags & PMF_DUCKED) + { + ent->client->anim_priority = ANIM_ATTACK; + ent->s.frame = FRAME_crattak1-1; + ent->client->anim_end = FRAME_crattak3; + } + else + { + ent->client->anim_priority = ANIM_REVERSE; + ent->s.frame = FRAME_wave08; + ent->client->anim_end = FRAME_wave01; + } +} + +void Weapon_Grenade (edict_t *ent) +{ + if ((ent->client->newweapon) && (ent->client->weaponstate == WEAPON_READY)) + { + ChangeWeapon (ent); + return; + } + + if (ent->client->weaponstate == WEAPON_ACTIVATING) + { + ent->client->weaponstate = WEAPON_READY; + ent->client->ps.gunframe = 16; + return; + } + + if (ent->client->weaponstate == WEAPON_READY) + { + if ( ((ent->client->latched_buttons|ent->client->buttons) & BUTTON_ATTACK) ) + { + ent->client->latched_buttons &= ~BUTTON_ATTACK; + if (ent->client->pers.inventory[ent->client->ammo_index]) + { + ent->client->ps.gunframe = 1; + ent->client->weaponstate = WEAPON_FIRING; + ent->client->grenade_time = 0; + } + else + { + if (level.time >= ent->pain_debounce_time) + { + gi.sound(ent, CHAN_VOICE, gi.soundindex("weapons/noammo.wav"), 1, ATTN_NORM, 0); + ent->pain_debounce_time = level.time + 1; + } + NoAmmoWeaponChange (ent); + } + return; + } + + if ((ent->client->ps.gunframe == 29) || (ent->client->ps.gunframe == 34) || (ent->client->ps.gunframe == 39) || (ent->client->ps.gunframe == 48)) + { + if (rand()&15) + return; + } + + if (++ent->client->ps.gunframe > 48) + ent->client->ps.gunframe = 16; + return; + } + + if (ent->client->weaponstate == WEAPON_FIRING) + { + if (ent->client->ps.gunframe == 5) + gi.sound(ent, CHAN_WEAPON, gi.soundindex("weapons/hgrena1b.wav"), 1, ATTN_NORM, 0); + + if (ent->client->ps.gunframe == 11) + { + if (!ent->client->grenade_time) + { + ent->client->grenade_time = level.time + GRENADE_TIMER + 0.2; + ent->client->weapon_sound = gi.soundindex("weapons/hgrenc1b.wav"); + } + + // they waited too long, detonate it in their hand + if (!ent->client->grenade_blew_up && level.time >= ent->client->grenade_time) + { + ent->client->weapon_sound = 0; + weapon_grenade_fire (ent, true); + ent->client->grenade_blew_up = true; + } + + if (ent->client->buttons & BUTTON_ATTACK) + return; + + if (ent->client->grenade_blew_up) + { + if (level.time >= ent->client->grenade_time) + { + ent->client->ps.gunframe = 15; + ent->client->grenade_blew_up = false; + } + else + { + return; + } + } + } + + if (ent->client->ps.gunframe == 12) + { + ent->client->weapon_sound = 0; + weapon_grenade_fire (ent, false); + } + + if ((ent->client->ps.gunframe == 15) && (level.time < ent->client->grenade_time)) + return; + + ent->client->ps.gunframe++; + + if (ent->client->ps.gunframe == 16) + { + ent->client->grenade_time = 0; + ent->client->weaponstate = WEAPON_READY; + } + } +} + +/* +====================================================================== + +GRENADE LAUNCHER + +====================================================================== +*/ + +void weapon_grenadelauncher_fire (edict_t *ent) +{ + vec3_t offset; + vec3_t forward, right; + vec3_t start; + int damage = 120; + float radius; + + radius = damage+40; + if (is_quad) + damage *= 4; + + VectorSet(offset, 8, 8, ent->viewheight-8); + AngleVectors (ent->client->v_angle, forward, right, NULL); + P_ProjectSource (ent->client, ent->s.origin, offset, forward, right, start); + + VectorScale (forward, -2, ent->client->kick_origin); + ent->client->kick_angles[0] = -1; + + fire_grenade (ent, start, forward, damage, 600, 2.5, radius); + + gi.WriteByte (svc_muzzleflash); + gi.WriteShort (ent-g_edicts); + gi.WriteByte (MZ_GRENADE | is_silenced); + gi.multicast (ent->s.origin, MULTICAST_PVS); + + ent->client->ps.gunframe++; + + PlayerNoise(ent, start, PNOISE_WEAPON); + + if (! ( (int)dmflags->value & DF_INFINITE_AMMO ) ) + ent->client->pers.inventory[ent->client->ammo_index]--; +} + +void Weapon_GrenadeLauncher (edict_t *ent) +{ + static int pause_frames[] = {34, 51, 59, 0}; + static int fire_frames[] = {6, 0}; + + Weapon_Generic (ent, 5, 16, 59, 64, pause_frames, fire_frames, weapon_grenadelauncher_fire); + + // RAFAEL + if (is_quadfire) + Weapon_Generic (ent, 5, 16, 59, 64, pause_frames, fire_frames, weapon_grenadelauncher_fire); +} + +/* +====================================================================== + +ROCKET + +====================================================================== +*/ +void fire_lockon_rocket (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, float damage_radius, int radius_damage); + + +void Weapon_RocketLauncher_Fire (edict_t *ent) +{ + vec3_t offset, start; + vec3_t forward, right; + int damage; + float damage_radius; + int radius_damage; + + damage = 100 + (int)(random() * 20.0); + radius_damage = 120; + damage_radius = 120; + if (is_quad) + { + damage *= 4; + radius_damage *= 4; + } + + AngleVectors (ent->client->v_angle, forward, right, NULL); + + VectorScale (forward, -2, ent->client->kick_origin); + ent->client->kick_angles[0] = -1; + + VectorSet(offset, 8, 8, ent->viewheight-8); + P_ProjectSource (ent->client, ent->s.origin, offset, forward, right, start); + if(ent->client->zc.aiming != 4) fire_rocket (ent, start, forward, damage, 650, damage_radius, radius_damage); + else + { + damage -= 20;//ロックオンは20ダメージ減り + fire_lockon_rocket (ent, start, forward, damage, 20, damage_radius, radius_damage); + } + + // send muzzle flash + gi.WriteByte (svc_muzzleflash); + gi.WriteShort (ent-g_edicts); + gi.WriteByte (MZ_ROCKET | is_silenced); + gi.multicast (ent->s.origin, MULTICAST_PVS); + + ent->client->ps.gunframe++; + + PlayerNoise(ent, start, PNOISE_WEAPON); + + if (! ( (int)dmflags->value & DF_INFINITE_AMMO ) ) + ent->client->pers.inventory[ent->client->ammo_index]--; + +// ent->client->ps.fov = 90; //ズーム解除 + ent->client->zc.aiming = 0; //ズーム不可 +} + +//ロックオンロケットランチャー +void Weapon_LockonRocketLauncher_Fire (edict_t *ent) +{ + vec3_t tmp,out,aim,min,max; + vec_t f; + trace_t rs_trace; + + if (ent->client->buttons & BUTTON_ATTACK) + { + ent->client->zc.lockon = false; //スナイパーにロックオン機能なし + if(ent->client->zc.aiming == 0) + { + gi.sound (ent, CHAN_WEAPON, gi.soundindex("weapons/sshotr1b.wav"), 1, ATTN_NONE, 0); + ent->client->zc.aiming = 3; + if(ent->client->zc.distance <10 || ent->client->zc.distance > 90) ent->client->zc.distance = 90; + ent->client->ps.fov = ent->client->zc.distance;//ズーム開始 + } + + VectorSet(max,8,8,8); + VectorSet(min,-8,-8,-8); + AngleVectors (ent->client->v_angle, aim, NULL , NULL); + VectorNormalize(aim); + VectorScale (aim, 8193, out); + VectorCopy(ent->s.origin,tmp); + if(ent->maxs[2] >= 32) tmp[2] += 22; + else tmp[2] -= 2; + VectorAdd(tmp,out,aim); + rs_trace = gi.trace (tmp, min, max, aim,ent, MASK_PLAYERSOLID); + if(rs_trace.ent != NULL) + { + if(Q_stricmp (rs_trace.ent->classname, "player") == 0) + { + if(ctf->value) + { + if(ent->client->resp.ctf_team != rs_trace.ent->client->resp.ctf_team) + { + ent->client->zc.lockon = true; + if(ent->client->zc.first_target != rs_trace.ent) + gi.sound (ent, CHAN_AUTO, gi.soundindex("3zb/locrloc.wav"), 1, ATTN_NORM, 0); + ent->client->zc.first_target = rs_trace.ent; + } + else ent->client->zc.first_target = NULL; + } + else + { + ent->client->zc.lockon = true; + if(ent->client->zc.first_target != rs_trace.ent) + gi.sound (ent, CHAN_AUTO, gi.soundindex("3zb/locrloc.wav"), 1, ATTN_NORM, 0); + ent->client->zc.first_target = rs_trace.ent; + } + return; //オートズーム反応せず + } + else ent->client->zc.first_target = NULL; + } + else ent->client->zc.first_target = NULL; + + if(ent->client->zc.autozoom ) + { + VectorSubtract(ent->s.origin,rs_trace.endpos,tmp); + f = VectorLength(tmp); + + if(f < 200) ent->client->zc.distance = 90; +// else if(f < 300) ent->client->zc.distance = 75; +// else if(f < 500) ent->client->zc.distance = 60; +// else if(f < 800) ent->client->zc.distance = 45; + else if(f < 1300) + { + ent->client->zc.distance = 90 - (f - 200) / 14.6;//30; + } + else ent->client->zc.distance = 14; + + if(ent->client->ps.fov != ent->client->zc.distance) + { + f = ent->client->ps.fov - ent->client->zc.distance; + if(f > 15 || f < -15 ) + gi.sound (ent, CHAN_AUTO, gi.soundindex("3zb/zoom.wav"), 1, ATTN_NORM, 0); + + ent->client->ps.fov = ent->client->zc.distance; + } + } + return; + } + ent->client->zc.aiming = 4; + Weapon_RocketLauncher_Fire(ent); +} + + +void Weapon_RocketLauncher (edict_t *ent) +{ + static int pause_frames[] = {25, 33, 42, 50, 0}; + static int fire_frames[] = {5, 0}; + + if(!(ent->client->buttons & BUTTON_ATTACK)) ent->client->zc.aiming = 0; //アクティベート0 + + if(0/*1*/) + { + Weapon_Generic (ent, 4, 12, 50, 54, pause_frames, fire_frames, Weapon_LockonRocketLauncher_Fire); + } + else + { + Weapon_Generic (ent, 4, 12, 50, 54, pause_frames, fire_frames, Weapon_RocketLauncher_Fire); + // RAFAEL + if (is_quadfire) + Weapon_Generic (ent, 4, 12, 50, 54, pause_frames, fire_frames, Weapon_RocketLauncher_Fire); + } + +} + + +/* +====================================================================== + +BLASTER / HYPERBLASTER + +====================================================================== +*/ + +void Blaster_Fire (edict_t *ent, vec3_t g_offset, int damage, qboolean hyper, int effect) +{ + vec3_t forward, right; + vec3_t start; + vec3_t offset; + + if (is_quad) + damage *= 4; + AngleVectors (ent->client->v_angle, forward, right, NULL); + VectorSet(offset, 24, 8, ent->viewheight-8); + + if(!(ent->svflags & SVF_MONSTER)) + { + VectorAdd (offset, g_offset, offset); + P_ProjectSource (ent->client, ent->s.origin, offset, forward, right, start); + + VectorScale (forward, -2, ent->client->kick_origin); + ent->client->kick_angles[0] = -1; + } + else + { + VectorSet(offset, 0, 0, ent->viewheight-8); + VectorAdd (offset, ent->s.origin, start); + } + + + + fire_blaster (ent, start, forward, damage, 1000, effect, hyper); + + // send muzzle flash + gi.WriteByte (svc_muzzleflash); + gi.WriteShort (ent-g_edicts); + if (hyper) + gi.WriteByte (MZ_HYPERBLASTER | is_silenced); + else + gi.WriteByte (MZ_BLASTER | is_silenced); + gi.multicast (ent->s.origin, MULTICAST_PVS); + + PlayerNoise(ent, start, PNOISE_WEAPON); +} + + +void Weapon_Blaster_Fire (edict_t *ent) +{ + int damage; + + if (deathmatch->value) + damage = 15; + else + damage = 10; + Blaster_Fire (ent, vec3_origin, damage, false, EF_BLASTER); + ent->client->ps.gunframe++; +} + +void Weapon_Blaster (edict_t *ent) +{ + static int pause_frames[] = {19, 32, 0}; + static int fire_frames[] = {5, 0}; + + Weapon_Generic (ent, 4, 8, 52, 55, pause_frames, fire_frames, Weapon_Blaster_Fire); + // RAFAEL + if (is_quadfire) + Weapon_Generic (ent, 4, 8, 52, 55, pause_frames, fire_frames, Weapon_Blaster_Fire); +} + + +void Weapon_HyperBlaster_Fire (edict_t *ent) +{ + float rotation; + vec3_t offset; + int effect; + int damage; + + ent->client->weapon_sound = gi.soundindex("weapons/hyprbl1a.wav"); + + if (!(ent->client->buttons & BUTTON_ATTACK)) + { + ent->client->ps.gunframe++; + } + else + { + if (! ent->client->pers.inventory[ent->client->ammo_index] ) + { + if (level.time >= ent->pain_debounce_time) + { + gi.sound(ent, CHAN_VOICE, gi.soundindex("weapons/noammo.wav"), 1, ATTN_NORM, 0); + ent->pain_debounce_time = level.time + 1; + } + NoAmmoWeaponChange (ent); + } + else + { + rotation = (ent->client->ps.gunframe - 5) * 2*M_PI/6; + offset[0] = -4 * sin(rotation); + offset[1] = 0; + offset[2] = 4 * cos(rotation); + + if ((ent->client->ps.gunframe == 6) || (ent->client->ps.gunframe == 9)) + effect = EF_HYPERBLASTER; + else + effect = 0; + if (deathmatch->value) + damage = 15; + else + damage = 20; + Blaster_Fire (ent, offset, damage, true, effect); + // ### Hentai ### BEGIN + + ent->client->anim_priority = ANIM_ATTACK; + if (ent->client->ps.pmove.pm_flags & PMF_DUCKED) + { + ent->s.frame = FRAME_crattak1 - 1; + ent->client->anim_end = FRAME_crattak9; + } + else + { + ent->s.frame = FRAME_attack1 - 1; + ent->client->anim_end = FRAME_attack8; + } + + // ### Hentai ### END + + if (! ( (int)dmflags->value & DF_INFINITE_AMMO ) ) + ent->client->pers.inventory[ent->client->ammo_index]--; + } + + ent->client->ps.gunframe++; + if (ent->client->ps.gunframe == 12 && ent->client->pers.inventory[ent->client->ammo_index]) + ent->client->ps.gunframe = 6; + } + + if (ent->client->ps.gunframe == 12) + { + gi.sound(ent, CHAN_AUTO, gi.soundindex("weapons/hyprbd1a.wav"), 1, ATTN_NORM, 0); + ent->client->weapon_sound = 0; + } + +} + +void Weapon_HyperBlaster (edict_t *ent) +{ + static int pause_frames[] = {0}; + static int fire_frames[] = {6, 7, 8, 9, 10, 11, 0}; + + Weapon_Generic (ent, 5, 20, 49, 53, pause_frames, fire_frames, Weapon_HyperBlaster_Fire); + + // RAFAEL + if (is_quadfire) + Weapon_Generic (ent, 5, 20, 49, 53, pause_frames, fire_frames, Weapon_HyperBlaster_Fire); + +} + +/* +====================================================================== + +MACHINEGUN / CHAINGUN + +====================================================================== +*/ + +void Machinegun_Fire (edict_t *ent) +{ + int i; + vec3_t start; + vec3_t forward, right; + vec3_t angles; + int damage = 8; + int kick = 2; + vec3_t offset; + + if (!(ent->client->buttons & BUTTON_ATTACK)) + { + ent->client->machinegun_shots = 0; + ent->client->ps.gunframe++; + return; + } + + if (ent->client->ps.gunframe == 5) + ent->client->ps.gunframe = 4; + else + ent->client->ps.gunframe = 5; + + if (ent->client->pers.inventory[ent->client->ammo_index] < 1) + { + ent->client->ps.gunframe = 6; + if (level.time >= ent->pain_debounce_time) + { + gi.sound(ent, CHAN_VOICE, gi.soundindex("weapons/noammo.wav"), 1, ATTN_NORM, 0); + ent->pain_debounce_time = level.time + 1; + } + NoAmmoWeaponChange (ent); + return; + } + + if (is_quad) + { + damage *= 4; + kick *= 4; + } + + for (i=1 ; i<3 ; i++) + { + ent->client->kick_origin[i] = crandom() * 0.35; + ent->client->kick_angles[i] = crandom() * 0.7; + } + ent->client->kick_origin[0] = crandom() * 0.35; + ent->client->kick_angles[0] = ent->client->machinegun_shots * -1.5; + + // raise the gun as it is firing + if (!deathmatch->value) + { + ent->client->machinegun_shots++; + if (ent->client->machinegun_shots > 9) + ent->client->machinegun_shots = 9; + } + + // get start / end positions + VectorAdd (ent->client->v_angle, ent->client->kick_angles, angles); + AngleVectors (angles, forward, right, NULL); + VectorSet(offset, 0, 8, ent->viewheight-8); + P_ProjectSource (ent->client, ent->s.origin, offset, forward, right, start); + fire_bullet (ent, start, forward, damage, kick, DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD, MOD_MACHINEGUN); + + gi.WriteByte (svc_muzzleflash); + gi.WriteShort (ent-g_edicts); + gi.WriteByte (MZ_MACHINEGUN | is_silenced); + gi.multicast (ent->s.origin, MULTICAST_PVS); + + PlayerNoise(ent, start, PNOISE_WEAPON); + + if (! ( (int)dmflags->value & DF_INFINITE_AMMO ) ) + ent->client->pers.inventory[ent->client->ammo_index]--; + + ent->client->anim_priority = ANIM_ATTACK; + if (ent->client->ps.pmove.pm_flags & PMF_DUCKED) + { + ent->s.frame = FRAME_crattak1 - (int) (random()+0.25); + ent->client->anim_end = FRAME_crattak9; + } + else + { + ent->s.frame = FRAME_attack1 - (int) (random()+0.25); + ent->client->anim_end = FRAME_attack8; + } +ent->client->weaponstate = WEAPON_READY; +} + +void Weapon_Machinegun (edict_t *ent) +{ + static int pause_frames[] = {23, 45, 0}; + static int fire_frames[] = {4, 5, 0}; + + Weapon_Generic (ent, 3, 5, 45, 49, pause_frames, fire_frames, Machinegun_Fire); + + // RAFAEL + if (is_quadfire) + Weapon_Generic (ent, 3, 5, 45, 49, pause_frames, fire_frames, Machinegun_Fire); +} + +void Chaingun_Fire (edict_t *ent) +{ + int i; + int shots; + vec3_t start; + vec3_t forward, right, up; + float r, u; + vec3_t offset; + int damage; + int kick = 2; + + if (deathmatch->value) + damage = 6; + else + damage = 8; + + if (ent->client->ps.gunframe == 5) + gi.sound(ent, CHAN_AUTO, gi.soundindex("weapons/chngnu1a.wav"), 1, ATTN_IDLE, 0); + + if ((ent->client->ps.gunframe == 14) && !(ent->client->buttons & BUTTON_ATTACK)) + { + ent->client->ps.gunframe = 32; + ent->client->weapon_sound = 0; + return; + } + else if ((ent->client->ps.gunframe == 21) && (ent->client->buttons & BUTTON_ATTACK) + && ent->client->pers.inventory[ent->client->ammo_index]) + { + ent->client->ps.gunframe = 15; + } + else + { + ent->client->ps.gunframe++; + } + + if (ent->client->ps.gunframe == 22) + { + ent->client->weapon_sound = 0; + gi.sound(ent, CHAN_AUTO, gi.soundindex("weapons/chngnd1a.wav"), 1, ATTN_IDLE, 0); + } + else + { + ent->client->weapon_sound = gi.soundindex("weapons/chngnl1a.wav"); + } + + if (ent->client->ps.gunframe <= 9) + shots = 1; + else if (ent->client->ps.gunframe <= 14) + { + if (ent->client->buttons & BUTTON_ATTACK) + shots = 2; + else + shots = 1; + } + else + shots = 3; + + if (ent->client->pers.inventory[ent->client->ammo_index] < shots) + shots = ent->client->pers.inventory[ent->client->ammo_index]; + + if (!shots) + { + if (level.time >= ent->pain_debounce_time) + { + gi.sound(ent, CHAN_VOICE, gi.soundindex("weapons/noammo.wav"), 1, ATTN_NORM, 0); + ent->pain_debounce_time = level.time + 1; + } + NoAmmoWeaponChange (ent); + return; + } + + if (is_quad) + { + damage *= 4; + kick *= 4; + } + + for (i=0 ; i<3 ; i++) + { + ent->client->kick_origin[i] = crandom() * 0.35; + ent->client->kick_angles[i] = crandom() * 0.7; + } + + for (i=0 ; iclient->v_angle, forward, right, up); + r = 7 + crandom()*4; + u = crandom()*4; + VectorSet(offset, 0, r, u + ent->viewheight-8); + P_ProjectSource (ent->client, ent->s.origin, offset, forward, right, start); + + fire_bullet (ent, start, forward, damage, kick, DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD, MOD_CHAINGUN); + } + + // send muzzle flash + gi.WriteByte (svc_muzzleflash); + gi.WriteShort (ent-g_edicts); + gi.WriteByte ((MZ_CHAINGUN1 + shots - 1) | is_silenced); + gi.multicast (ent->s.origin, MULTICAST_PVS); + + PlayerNoise(ent, start, PNOISE_WEAPON); + + // ### Hentai ### BEGIN + + ent->client->anim_priority = ANIM_ATTACK; + if (ent->client->ps.pmove.pm_flags & PMF_DUCKED) + { + ent->s.frame = FRAME_crattak1 - 1 + (ent->client->ps.gunframe % 3); + ent->client->anim_end = FRAME_crattak9; + } + else + { + ent->s.frame = FRAME_attack1 - 1 + (ent->client->ps.gunframe % 3); + ent->client->anim_end = FRAME_attack8; + } + + + // ### Hentai ### END + + if (! ( (int)dmflags->value & DF_INFINITE_AMMO ) ) + ent->client->pers.inventory[ent->client->ammo_index] -= shots; +} + +void Gatringgun_Fire (edict_t *ent) +{ + int i; + int shots; + vec3_t start; + vec3_t forward, right, up; + float r, u; + vec3_t offset; + int damage; + int kick = 2; + + if (deathmatch->value) + damage = 8; + else + damage = 8; + + if (ent->client->ps.gunframe == 5) + gi.sound(ent, CHAN_AUTO, gi.soundindex("weapons/chngnu1a.wav"), 1, ATTN_IDLE, 0); + + if ((ent->client->ps.gunframe == 14) && !(ent->client->buttons & BUTTON_ATTACK)) + { + ent->client->ps.gunframe = 32; + ent->client->weapon_sound = 0; + return; + } + else if ((ent->client->ps.gunframe == 21) && (ent->client->buttons & BUTTON_ATTACK) + && ent->client->pers.inventory[ent->client->ammo_index]) + { + ent->client->ps.gunframe = 15; + } + else + { + ent->client->ps.gunframe++; + } + + if (ent->client->ps.gunframe == 22) + { + ent->client->weapon_sound = 0; + gi.sound(ent, CHAN_AUTO, gi.soundindex("weapons/chngnd1a.wav"), 1, ATTN_IDLE, 0); + } + else + { + ent->client->weapon_sound = gi.soundindex("weapons/chngnl1a.wav"); + } + + if (ent->client->ps.gunframe <= 9) + shots = 10;//1; + else if (ent->client->ps.gunframe <= 14) + { + if (ent->client->buttons & BUTTON_ATTACK) + shots = 10;//2; + else + shots = 10;//1; + } + else + shots = 3;//3; + + if (ent->client->pers.inventory[ent->client->ammo_index] < shots) + shots = ent->client->pers.inventory[ent->client->ammo_index]; + + if (shots == 10 ) return; + + if (!shots) + { + if (level.time >= ent->pain_debounce_time) + { + gi.sound(ent, CHAN_VOICE, gi.soundindex("weapons/noammo.wav"), 1, ATTN_NORM, 0); + ent->pain_debounce_time = level.time + 1; + } + NoAmmoWeaponChange (ent); + return; + } + + if (is_quad) + { + damage *= 4; + kick *= 4; + } + + for (i=0 ; i<3 ; i++) + { + ent->client->kick_origin[i] = crandom() * 0.35; + ent->client->kick_angles[i] = crandom() * 0.7; + } + + for (i=0 ; iclient->v_angle, forward, right, up); + r = 7 + crandom()*4; + u = crandom()*4; + VectorSet(offset, 0, r, u + ent->viewheight-8); + P_ProjectSource (ent->client, ent->s.origin, offset, forward, right, start); + + fire_bullet (ent, start, forward, damage, kick, DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD, MOD_CHAINGUN); + } + + if(is_silenced) u = 0.5; + else u = 1.0; + + gi.sound (ent, CHAN_AUTO, gi.soundindex("3zb/gatgf.wav"), u, ATTN_NORM, 0); + + // send muzzle flash + gi.WriteByte (svc_muzzleflash); + gi.WriteShort (ent-g_edicts); + gi.WriteByte ((MZ_BLASTER) | MZ_SILENCED); + gi.multicast (ent->s.origin, MULTICAST_PVS); + + PlayerNoise(ent, start, PNOISE_WEAPON); + + // ### Hentai ### BEGIN + + ent->client->anim_priority = ANIM_ATTACK; + if (ent->client->ps.pmove.pm_flags & PMF_DUCKED) + { + ent->s.frame = FRAME_crattak1 - 1 + (ent->client->ps.gunframe % 3); + ent->client->anim_end = FRAME_crattak9; + } + else + { + ent->s.frame = FRAME_attack1 - 1 + (ent->client->ps.gunframe % 3); + ent->client->anim_end = FRAME_attack8; + } + + + // ### Hentai ### END + + if (! ( (int)dmflags->value & DF_INFINITE_AMMO ) ) + ent->client->pers.inventory[ent->client->ammo_index] -= 1;//shots; +} + +void Weapon_Chaingun (edict_t *ent) +{ + static int pause_frames[] = {38, 43, 51, 61, 0}; + static int fire_frames[] = {5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 0}; + + if(0) Weapon_Generic (ent, 4, 31, 61, 64, pause_frames, fire_frames, Gatringgun_Fire); + else + { + Weapon_Generic (ent, 4, 31, 61, 64, pause_frames, fire_frames, Chaingun_Fire); + // RAFAEL + if (is_quadfire) + Weapon_Generic (ent, 4, 31, 61, 64, pause_frames, fire_frames, Chaingun_Fire); + + } +} + + +/* +====================================================================== + +SHOTGUN / SUPERSHOTGUN + +====================================================================== +*/ + +void weapon_shotgun_fire (edict_t *ent) +{ + vec3_t start; + vec3_t forward, right; + vec3_t offset; + int damage = 4; + int kick = 8; + + if (ent->client->ps.gunframe == 9) + { + ent->client->ps.gunframe++; + return; + } + + AngleVectors (ent->client->v_angle, forward, right, NULL); + + VectorScale (forward, -2, ent->client->kick_origin); + ent->client->kick_angles[0] = -2; + + VectorSet(offset, 0, 8, ent->viewheight-8); + P_ProjectSource (ent->client, ent->s.origin, offset, forward, right, start); + + if (is_quad) + { + damage *= 4; + kick *= 4; + } + + if (deathmatch->value) + fire_shotgun (ent, start, forward, damage, kick, 500, 500, DEFAULT_DEATHMATCH_SHOTGUN_COUNT, MOD_SHOTGUN); + else + fire_shotgun (ent, start, forward, damage, kick, 500, 500, DEFAULT_SHOTGUN_COUNT, MOD_SHOTGUN); + + // send muzzle flash + gi.WriteByte (svc_muzzleflash); + gi.WriteShort (ent-g_edicts); + gi.WriteByte (MZ_SHOTGUN | is_silenced); + gi.multicast (ent->s.origin, MULTICAST_PVS); + + ent->client->ps.gunframe++; + PlayerNoise(ent, start, PNOISE_WEAPON); + + if (! ( (int)dmflags->value & DF_INFINITE_AMMO ) ) + ent->client->pers.inventory[ent->client->ammo_index]--; +} + +void Weapon_Shotgun (edict_t *ent) +{ + static int pause_frames[] = {22, 28, 34, 0}; + static int fire_frames[] = {8, 9, 0}; + + Weapon_Generic (ent, 7, 18, 36, 39, pause_frames, fire_frames, weapon_shotgun_fire); + // RAFAEL + if (is_quadfire) + Weapon_Generic (ent, 7, 18, 36, 39, pause_frames, fire_frames, weapon_shotgun_fire); +} + + +void weapon_supershotgun_fire (edict_t *ent) +{ + vec3_t start; + vec3_t forward, right; + vec3_t offset; + vec3_t v; + int damage = 6; + int kick = 12; + + AngleVectors (ent->client->v_angle, forward, right, NULL); + + VectorScale (forward, -2, ent->client->kick_origin); + ent->client->kick_angles[0] = -2; + + VectorSet(offset, 0, 8, ent->viewheight-8); + P_ProjectSource (ent->client, ent->s.origin, offset, forward, right, start); + + if (is_quad) + { + damage *= 4; + kick *= 4; + } + + v[PITCH] = ent->client->v_angle[PITCH]; + v[YAW] = ent->client->v_angle[YAW] - 5; + v[ROLL] = ent->client->v_angle[ROLL]; + AngleVectors (v, forward, NULL, NULL); + fire_shotgun (ent, start, forward, damage, kick, DEFAULT_SHOTGUN_HSPREAD, DEFAULT_SHOTGUN_VSPREAD, DEFAULT_SSHOTGUN_COUNT/2, MOD_SSHOTGUN); + v[YAW] = ent->client->v_angle[YAW] + 5; + AngleVectors (v, forward, NULL, NULL); + fire_shotgun (ent, start, forward, damage, kick, DEFAULT_SHOTGUN_HSPREAD, DEFAULT_SHOTGUN_VSPREAD, DEFAULT_SSHOTGUN_COUNT/2, MOD_SSHOTGUN); + + // send muzzle flash + gi.WriteByte (svc_muzzleflash); + gi.WriteShort (ent-g_edicts); + gi.WriteByte (MZ_SSHOTGUN | is_silenced); + gi.multicast (ent->s.origin, MULTICAST_PVS); + + ent->client->ps.gunframe++; + PlayerNoise(ent, start, PNOISE_WEAPON); + + if (! ( (int)dmflags->value & DF_INFINITE_AMMO ) ) + ent->client->pers.inventory[ent->client->ammo_index] -= 2; +} + +void Weapon_SuperShotgun (edict_t *ent) +{ + static int pause_frames[] = {29, 42, 57, 0}; + static int fire_frames[] = {7, 0}; + + Weapon_Generic (ent, 6, 17, 57, 61, pause_frames, fire_frames, weapon_supershotgun_fire); + // RAFAEL + if (is_quadfire) + Weapon_Generic (ent, 6, 17, 57, 61, pause_frames, fire_frames, weapon_supershotgun_fire); +} + + + +/* +====================================================================== + +RAILGUN + +====================================================================== +*/ +void fire_sniperail (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick); + +void RSight_think(edict_t *ent) +{ + trace_t rs_trace; + vec3_t aim,out,tmp; + vec3_t max,min; + + vec_t f; + + if(ent->owner->client->ps.gunframe != 4 || ent->owner->deadflag) + { + G_FreeEdict(ent); + return; + } + VectorSet(max,4,4,4); + VectorSet(min,-4,-4,-4); + AngleVectors (ent->owner->client->v_angle, aim, NULL , NULL); + VectorNormalize(aim); + VectorScale (aim, 8193, out); + VectorCopy(ent->owner->s.origin,tmp); + if(ent->owner->maxs[2] >= 32) tmp[2] += 22; + else tmp[2] -= 2; + VectorAdd(tmp,out,aim); + rs_trace = gi.trace (tmp, min, max, aim,ent->owner, MASK_PLAYERSOLID); + VectorCopy(rs_trace.endpos,ent->s.origin); + ent->nextthink = level.time + FRAMETIME; + + if(rs_trace.ent != NULL) + { + if(Q_stricmp (rs_trace.ent->classname, "player") == 0) return;//オートズーム反応せず + } + + if(ent->owner->client->zc.autozoom ) + { + VectorSubtract(ent->s.origin,ent->owner->s.origin,tmp); + f = VectorLength(tmp); + + if(f < 100) ent->owner->client->zc.distance = 90; +// else if(f < 300) ent->owner->client->zc.distance = 75; +// else if(f < 500) ent->owner->client->zc.distance = 60; +// else if(f < 800) ent->owner->client->zc.distance = 45; + else if(f < 1000) + { + ent->owner->client->zc.distance = 90 - (f - 100) / 12;//30; + } + else ent->owner->client->zc.distance = 15; + + if(ent->owner->client->ps.fov != ent->owner->client->zc.distance) + { + f = ent->owner->client->ps.fov - ent->owner->client->zc.distance; + if( f > 15 || f < -15) + gi.sound (ent->owner, CHAN_AUTO, gi.soundindex("3zb/zoom.wav"), 1, ATTN_NORM, 0); + + ent->owner->client->ps.fov = ent->owner->client->zc.distance; + } + } +} + + +void weapon_railgun_fire (edict_t *ent) +{ + vec3_t start; + vec3_t forward, right; + vec3_t offset; + int damage; + int kick; + + if (deathmatch->value) + { // normal damage is too extreme in dm + damage = 100; + kick = 200; + } + else + { + damage = 150; + kick = 250; + } + + if (is_quad) + { + damage *= 4; + kick *= 4; + } + + AngleVectors (ent->client->v_angle, forward, right, NULL); + + VectorScale (forward, -3, ent->client->kick_origin); + ent->client->kick_angles[0] = -3; + + VectorSet(offset, 0, 7, ent->viewheight-8); + + P_ProjectSource (ent->client, ent->s.origin, offset, forward, right, start); + + if(ent->client->zc.aiming == 0) fire_rail (ent, start, forward, damage, kick); + else + { + damage += 20; + fire_sniperail (ent, start, forward, damage, kick); + } + +// gi.bprintf(PRINT_HIGH,"jj %i\n",ent->moveinfo.sound_start); + + // send muzzle flash + gi.WriteByte (svc_muzzleflash); + gi.WriteShort (ent-g_edicts); + gi.WriteByte (MZ_RAILGUN | is_silenced); + gi.multicast (ent->s.origin, MULTICAST_PVS); + + ent->client->ps.gunframe++; + PlayerNoise(ent, start, PNOISE_WEAPON); + + if (! ( (int)dmflags->value & DF_INFINITE_AMMO ) ) + ent->client->pers.inventory[ent->client->ammo_index]--; + +// ent->client->ps.fov = 90; //ズーム解除 + ent->client->zc.aiming = 0; //ズーム不可 +} +//スナイパー用railガン +void Weapon_SnipeRailgun (edict_t *ent) +{ + edict_t *sight; + + if (ent->client->buttons & BUTTON_ATTACK) + { + ent->client->zc.lockon = false; //スナイパーにロックオン機能なし + if( ent->client->zc.aiming == 0) + { + //サイトの作成 + sight = G_Spawn(); + VectorClear (sight->mins); + VectorClear (sight->maxs); + sight->movetype = MOVETYPE_FLYMISSILE; + sight->solid = SOLID_NOT; + sight->owner = ent; + sight->s.modelindex = gi.modelindex ("sprites/zsight.sp2"); + sight->s.effects = 0; + sight->s.renderfx = RF_SHELL_RED; + sight->think = RSight_think; + sight->nextthink = level.time + FRAMETIME; + sight->classname = "rail sight"; + if( ent->client->resp.ctf_team == CTF_TEAM2 && ctf->value) + { + sight->s.frame = 1; + } + else sight->s.frame = 0; + + gi.sound (ent, CHAN_WEAPON, gi.soundindex("weapons/sshotr1b.wav"), 1, ATTN_NONE, 0); + ent->client->zc.aiming = 1; + if(ent->client->zc.distance <10 || ent->client->zc.distance > 90) ent->client->zc.distance = 90; + ent->client->ps.fov = ent->client->zc.distance;//ズーム開始 + } + return; + } + +// if (ent->client->buttons & BUTTON_ATTACK) return; + ent->client->zc.aiming = 2; + weapon_railgun_fire(ent); +} + +void Weapon_Railgun (edict_t *ent) +{ + static int pause_frames[] = {56, 0}; + static int fire_frames[] = {4, 0}; + + if(!(ent->client->buttons & BUTTON_ATTACK)) ent->client->zc.aiming = 0; //アクティベート0 + + if(0) + { + Weapon_Generic (ent, 3, 18, 56, 61, pause_frames, fire_frames, Weapon_SnipeRailgun/*weapon_railgun_fire*/); + } + else + { + Weapon_Generic (ent, 3, 18, 56, 61, pause_frames, fire_frames, weapon_railgun_fire); + // RAFAEL + if (is_quadfire) + Weapon_Generic (ent, 3, 18, 56, 61, pause_frames, fire_frames, weapon_railgun_fire); + } +} + + +/* +====================================================================== + +BFG10K + +====================================================================== +*/ + +void weapon_bfg_fire (edict_t *ent) +{ + vec3_t offset, start; + vec3_t forward, right; + int damage; + float damage_radius = 1000; + + if (deathmatch->value) + damage = 200; + else + damage = 500; + + if (ent->client->ps.gunframe == 9) + { + // send muzzle flash + gi.WriteByte (svc_muzzleflash); + gi.WriteShort (ent-g_edicts); + gi.WriteByte (MZ_BFG | is_silenced); + gi.multicast (ent->s.origin, MULTICAST_PVS); + + ent->client->ps.gunframe++; + + PlayerNoise(ent, start, PNOISE_WEAPON); + return; + } + + // cells can go down during windup (from power armor hits), so + // check again and abort firing if we don't have enough now + if (ent->client->pers.inventory[ent->client->ammo_index] < 50) + { + ent->client->ps.gunframe++; + return; + } + + if (is_quad) + damage *= 4; + + AngleVectors (ent->client->v_angle, forward, right, NULL); + + VectorScale (forward, -2, ent->client->kick_origin); + + // make a big pitch kick with an inverse fall + ent->client->v_dmg_pitch = -40; + ent->client->v_dmg_roll = crandom()*8; + ent->client->v_dmg_time = level.time + DAMAGE_TIME; + + VectorSet(offset, 8, 8, ent->viewheight-8); + P_ProjectSource (ent->client, ent->s.origin, offset, forward, right, start); + fire_bfg (ent, start, forward, damage, 400, damage_radius); + + ent->client->ps.gunframe++; + + PlayerNoise(ent, start, PNOISE_WEAPON); + + if (! ( (int)dmflags->value & DF_INFINITE_AMMO ) ) + ent->client->pers.inventory[ent->client->ammo_index] -= 50; +} + +void Weapon_BFG (edict_t *ent) +{ + static int pause_frames[] = {39, 45, 50, 55, 0}; + static int fire_frames[] = {9, 17, 0}; + + Weapon_Generic (ent, 8, 32, 55, 58, pause_frames, fire_frames, weapon_bfg_fire); + // RAFAEL + if (is_quadfire) + Weapon_Generic (ent, 8, 32, 55, 58, pause_frames, fire_frames, weapon_bfg_fire); +} + + +//====================================================================== +// RAFAEL +/* + RipperGun +*/ + +void weapon_ionripper_fire (edict_t *ent) +{ + vec3_t start; + vec3_t forward, right; + vec3_t offset; + vec3_t tempang; + int damage; + int kick; + + if (deathmatch->value) + { + // tone down for deathmatch + damage = 30; + kick = 40; + } + else + { + damage = 50; + kick = 60; + } + + if (is_quad) + { + damage *= 4; + kick *= 4; + } + + VectorCopy (ent->client->v_angle, tempang); + tempang[YAW] += crandom(); + + AngleVectors (tempang, forward, right, NULL); + + VectorScale (forward, -3, ent->client->kick_origin); + ent->client->kick_angles[0] = -3; + + // VectorSet (offset, 0, 7, ent->viewheight - 8); + VectorSet (offset, 16, 7, ent->viewheight - 8); + + P_ProjectSource (ent->client, ent->s.origin, offset, forward, right, start); + + fire_ionripper (ent, start, forward, damage, 500, EF_IONRIPPER); + + // send muzzle flash + gi.WriteByte (svc_muzzleflash); + gi.WriteShort (ent - g_edicts); + gi.WriteByte (MZ_IONRIPPER | is_silenced); + gi.multicast (ent->s.origin, MULTICAST_PVS); + + ent->client->ps.gunframe++; + PlayerNoise (ent, start, PNOISE_WEAPON); + + if (! ( (int)dmflags->value & DF_INFINITE_AMMO ) ) + ent->client->pers.inventory[ent->client->ammo_index] -= ent->client->pers.weapon->quantity; + + if (ent->client->pers.inventory[ent->client->ammo_index] < 0) + ent->client->pers.inventory[ent->client->ammo_index] = 0; +} + + +void Weapon_Ionripper (edict_t *ent) +{ + static int pause_frames[] = {36, 0}; + static int fire_frames[] = {5, 0}; + + Weapon_Generic (ent, 4, 6, 36, 39, pause_frames, fire_frames, weapon_ionripper_fire); + + if (is_quadfire) + Weapon_Generic (ent, 4, 6, 36, 39, pause_frames, fire_frames, weapon_ionripper_fire); +} + + +// +// Phalanx +// + +void weapon_phalanx_fire (edict_t *ent) +{ + vec3_t start; + vec3_t forward, right, up; + vec3_t offset; + vec3_t v; + int kick = 12; + int damage; + float damage_radius; + int radius_damage; + + damage = 70 + (int)(random() * 10.0); + radius_damage = 120; + damage_radius = 120; + + if (is_quad) + { + damage *= 4; + radius_damage *= 4; + } + + AngleVectors (ent->client->v_angle, forward, right, NULL); + + VectorScale (forward, -2, ent->client->kick_origin); + ent->client->kick_angles[0] = -2; + + VectorSet(offset, 0, 8, ent->viewheight-8); + P_ProjectSource (ent->client, ent->s.origin, offset, forward, right, start); + + if (ent->client->ps.gunframe == 8) + { + v[PITCH] = ent->client->v_angle[PITCH]; + v[YAW] = ent->client->v_angle[YAW] - 1.5; + v[ROLL] = ent->client->v_angle[ROLL]; + AngleVectors (v, forward, right, up); + + radius_damage = 30; + damage_radius = 120; + + fire_plasma (ent, start, forward, damage, 725, damage_radius, radius_damage); + + if (! ( (int)dmflags->value & DF_INFINITE_AMMO ) ) + ent->client->pers.inventory[ent->client->ammo_index]--; + } + else + { + v[PITCH] = ent->client->v_angle[PITCH]; + v[YAW] = ent->client->v_angle[YAW] + 1.5; + v[ROLL] = ent->client->v_angle[ROLL]; + AngleVectors (v, forward, right, up); + fire_plasma (ent, start, forward, damage, 725, damage_radius, radius_damage); + + // send muzzle flash + gi.WriteByte (svc_muzzleflash); + gi.WriteShort (ent-g_edicts); + gi.WriteByte (MZ_PHALANX | is_silenced); + gi.multicast (ent->s.origin, MULTICAST_PVS); + + PlayerNoise(ent, start, PNOISE_WEAPON); + } + + ent->client->ps.gunframe++; + +} + +void Weapon_Phalanx (edict_t *ent) +{ + static int pause_frames[] = {29, 42, 55, 0}; + static int fire_frames[] = {7, 8, 0}; + + Weapon_Generic (ent, 5, 20, 58, 63, pause_frames, fire_frames, weapon_phalanx_fire); + + if (is_quadfire) + Weapon_Generic (ent, 5, 20, 58, 63, pause_frames, fire_frames, weapon_phalanx_fire); + +} + +/* +====================================================================== + +TRAP + +====================================================================== +*/ + +#define TRAP_TIMER 5.0 +#define TRAP_MINSPEED 300 +#define TRAP_MAXSPEED 700 + +void weapon_trap_fire (edict_t *ent, qboolean held) +{ + vec3_t offset; + vec3_t forward, right; + vec3_t start; + int damage = 125; + float timer; + int speed; + float radius; + + radius = damage+40; + if (is_quad) + damage *= 4; + + VectorSet(offset, 8, 8, ent->viewheight-8); + AngleVectors (ent->client->v_angle, forward, right, NULL); + P_ProjectSource (ent->client, ent->s.origin, offset, forward, right, start); + + timer = ent->client->grenade_time - level.time; + speed = GRENADE_MINSPEED + (GRENADE_TIMER - timer) * ((GRENADE_MAXSPEED - GRENADE_MINSPEED) / GRENADE_TIMER); + // fire_grenade2 (ent, start, forward, damage, speed, timer, radius, held); + fire_trap (ent, start, forward, damage, speed, timer, radius, held); + +// you don't get infinite traps! ZOID +// if (! ( (int)dmflags->value & DF_INFINITE_AMMO ) ) + + ent->client->pers.inventory[ent->client->ammo_index]--; + + ent->client->grenade_time = level.time + 1.0; +} + +void Weapon_Trap (edict_t *ent) +{ + if ((ent->client->newweapon) && (ent->client->weaponstate == WEAPON_READY)) + { + ChangeWeapon (ent); + return; + } + + if (ent->client->weaponstate == WEAPON_ACTIVATING) + { + ent->client->weaponstate = WEAPON_READY; + ent->client->ps.gunframe = 16; + return; + } + + if (ent->client->weaponstate == WEAPON_READY) + { + if ( ((ent->client->latched_buttons|ent->client->buttons) & BUTTON_ATTACK) ) + { + ent->client->latched_buttons &= ~BUTTON_ATTACK; + if (ent->client->pers.inventory[ent->client->ammo_index]) + { + ent->client->ps.gunframe = 1; + ent->client->weaponstate = WEAPON_FIRING; + ent->client->grenade_time = 0; + } + else + { + if (level.time >= ent->pain_debounce_time) + { + gi.sound(ent, CHAN_VOICE, gi.soundindex("weapons/noammo.wav"), 1, ATTN_NORM, 0); + ent->pain_debounce_time = level.time + 1; + } + NoAmmoWeaponChange (ent); + } + return; + } + + if ((ent->client->ps.gunframe == 29) || (ent->client->ps.gunframe == 34) || (ent->client->ps.gunframe == 39) || (ent->client->ps.gunframe == 48)) + { + if (rand()&15) + return; + } + + if (++ent->client->ps.gunframe > 48) + ent->client->ps.gunframe = 16; + return; + } + + if (ent->client->weaponstate == WEAPON_FIRING) + { + if (ent->client->ps.gunframe == 5) + // RAFAEL 16-APR-98 + // gi.sound(ent, CHAN_WEAPON, gi.soundindex("weapons/hgrena1b.wav"), 1, ATTN_NORM, 0); + gi.sound(ent, CHAN_WEAPON, gi.soundindex("weapons/trapcock.wav"), 1, ATTN_NORM, 0); + // END 16-APR-98 + + if (ent->client->ps.gunframe == 11) + { + if (!ent->client->grenade_time) + { + ent->client->grenade_time = level.time + GRENADE_TIMER + 0.2; + // RAFAEL 16-APR-98 + ent->client->weapon_sound = gi.soundindex("weapons/traploop.wav"); + // END 16-APR-98 + } + + // they waited too long, detonate it in their hand + if (!ent->client->grenade_blew_up && level.time >= ent->client->grenade_time) + { + ent->client->weapon_sound = 0; + weapon_trap_fire (ent, true); + ent->client->grenade_blew_up = true; + } + + if (ent->client->buttons & BUTTON_ATTACK) + return; + + if (ent->client->grenade_blew_up) + { + if (level.time >= ent->client->grenade_time) + { + ent->client->ps.gunframe = 15; + ent->client->grenade_blew_up = false; + } + else + { + return; + } + } + } + + if (ent->client->ps.gunframe == 12) + { + ent->client->weapon_sound = 0; + weapon_trap_fire (ent, false); + if (ent->client->pers.inventory[ent->client->ammo_index] == 0) + NoAmmoWeaponChange (ent); + } + + if ((ent->client->ps.gunframe == 15) && (level.time < ent->client->grenade_time)) + return; + + ent->client->ps.gunframe++; + + if (ent->client->ps.gunframe == 16) + { + ent->client->grenade_time = 0; + ent->client->weaponstate = WEAPON_READY; + } + } +} diff --git a/src/q_shared.c b/src/q_shared.c new file mode 100644 index 0000000..6a3ef0a --- /dev/null +++ b/src/q_shared.c @@ -0,0 +1,1400 @@ +#include "q_shared.h" + +#define DEG2RAD( a ) ( a * M_PI ) / 180.0F + +vec3_t vec3_origin = {0,0,0}; + +//============================================================================ + +#ifdef _WIN32 +#pragma optimize( "", off ) +#endif + +void RotatePointAroundVector( vec3_t dst, const vec3_t dir, const vec3_t point, float degrees ) +{ + float m[3][3]; + float im[3][3]; + float zrot[3][3]; + float tmpmat[3][3]; + float rot[3][3]; + int i; + vec3_t vr, vup, vf; + + vf[0] = dir[0]; + vf[1] = dir[1]; + vf[2] = dir[2]; + + PerpendicularVector( vr, dir ); + CrossProduct( vr, vf, vup ); + + m[0][0] = vr[0]; + m[1][0] = vr[1]; + m[2][0] = vr[2]; + + m[0][1] = vup[0]; + m[1][1] = vup[1]; + m[2][1] = vup[2]; + + m[0][2] = vf[0]; + m[1][2] = vf[1]; + m[2][2] = vf[2]; + + memcpy( im, m, sizeof( im ) ); + + im[0][1] = m[1][0]; + im[0][2] = m[2][0]; + im[1][0] = m[0][1]; + im[1][2] = m[2][1]; + im[2][0] = m[0][2]; + im[2][1] = m[1][2]; + + memset( zrot, 0, sizeof( zrot ) ); + zrot[0][0] = zrot[1][1] = zrot[2][2] = 1.0F; + + zrot[0][0] = cos( DEG2RAD( degrees ) ); + zrot[0][1] = sin( DEG2RAD( degrees ) ); + zrot[1][0] = -sin( DEG2RAD( degrees ) ); + zrot[1][1] = cos( DEG2RAD( degrees ) ); + + R_ConcatRotations( m, zrot, tmpmat ); + R_ConcatRotations( tmpmat, im, rot ); + + for ( i = 0; i < 3; i++ ) + { + dst[i] = rot[i][0] * point[0] + rot[i][1] * point[1] + rot[i][2] * point[2]; + } +} + +#ifdef _WIN32 +#pragma optimize( "", on ) +#endif + + + +void AngleVectors (vec3_t angles, vec3_t forward, vec3_t right, vec3_t up) +{ + float angle; + static float sr, sp, sy, cr, cp, cy; + // static to help MS compiler fp bugs + + angle = angles[YAW] * (M_PI*2 / 360); + sy = sin(angle); + cy = cos(angle); + angle = angles[PITCH] * (M_PI*2 / 360); + sp = sin(angle); + cp = cos(angle); + angle = angles[ROLL] * (M_PI*2 / 360); + sr = sin(angle); + cr = cos(angle); + + if (forward) + { + forward[0] = cp*cy; + forward[1] = cp*sy; + forward[2] = -sp; + } + if (right) + { + right[0] = (-1*sr*sp*cy+-1*cr*-sy); + right[1] = (-1*sr*sp*sy+-1*cr*cy); + right[2] = -1*sr*cp; + } + if (up) + { + up[0] = (cr*sp*cy+-sr*-sy); + up[1] = (cr*sp*sy+-sr*cy); + up[2] = cr*cp; + } +} + + +void ProjectPointOnPlane( vec3_t dst, const vec3_t p, const vec3_t normal ) +{ + float d; + vec3_t n; + float inv_denom; + + inv_denom = 1.0F / DotProduct( normal, normal ); + + d = DotProduct( normal, p ) * inv_denom; + + n[0] = normal[0] * inv_denom; + n[1] = normal[1] * inv_denom; + n[2] = normal[2] * inv_denom; + + dst[0] = p[0] - d * n[0]; + dst[1] = p[1] - d * n[1]; + dst[2] = p[2] - d * n[2]; +} + +/* +** assumes "src" is normalized +*/ +void PerpendicularVector( vec3_t dst, const vec3_t src ) +{ + int pos; + int i; + float minelem = 1.0F; + vec3_t tempvec; + + /* + ** find the smallest magnitude axially aligned vector + */ + for ( pos = 0, i = 0; i < 3; i++ ) + { + if ( fabs( src[i] ) < minelem ) + { + pos = i; + minelem = fabs( src[i] ); + } + } + tempvec[0] = tempvec[1] = tempvec[2] = 0.0F; + tempvec[pos] = 1.0F; + + /* + ** project the point onto the plane defined by src + */ + ProjectPointOnPlane( dst, tempvec, src ); + + /* + ** normalize the result + */ + VectorNormalize( dst ); +} + + + +/* +================ +R_ConcatRotations +================ +*/ +void R_ConcatRotations (float in1[3][3], float in2[3][3], float out[3][3]) +{ + out[0][0] = in1[0][0] * in2[0][0] + in1[0][1] * in2[1][0] + + in1[0][2] * in2[2][0]; + out[0][1] = in1[0][0] * in2[0][1] + in1[0][1] * in2[1][1] + + in1[0][2] * in2[2][1]; + out[0][2] = in1[0][0] * in2[0][2] + in1[0][1] * in2[1][2] + + in1[0][2] * in2[2][2]; + out[1][0] = in1[1][0] * in2[0][0] + in1[1][1] * in2[1][0] + + in1[1][2] * in2[2][0]; + out[1][1] = in1[1][0] * in2[0][1] + in1[1][1] * in2[1][1] + + in1[1][2] * in2[2][1]; + out[1][2] = in1[1][0] * in2[0][2] + in1[1][1] * in2[1][2] + + in1[1][2] * in2[2][2]; + out[2][0] = in1[2][0] * in2[0][0] + in1[2][1] * in2[1][0] + + in1[2][2] * in2[2][0]; + out[2][1] = in1[2][0] * in2[0][1] + in1[2][1] * in2[1][1] + + in1[2][2] * in2[2][1]; + out[2][2] = in1[2][0] * in2[0][2] + in1[2][1] * in2[1][2] + + in1[2][2] * in2[2][2]; +} + + +/* +================ +R_ConcatTransforms +================ +*/ +void R_ConcatTransforms (float in1[3][4], float in2[3][4], float out[3][4]) +{ + out[0][0] = in1[0][0] * in2[0][0] + in1[0][1] * in2[1][0] + + in1[0][2] * in2[2][0]; + out[0][1] = in1[0][0] * in2[0][1] + in1[0][1] * in2[1][1] + + in1[0][2] * in2[2][1]; + out[0][2] = in1[0][0] * in2[0][2] + in1[0][1] * in2[1][2] + + in1[0][2] * in2[2][2]; + out[0][3] = in1[0][0] * in2[0][3] + in1[0][1] * in2[1][3] + + in1[0][2] * in2[2][3] + in1[0][3]; + out[1][0] = in1[1][0] * in2[0][0] + in1[1][1] * in2[1][0] + + in1[1][2] * in2[2][0]; + out[1][1] = in1[1][0] * in2[0][1] + in1[1][1] * in2[1][1] + + in1[1][2] * in2[2][1]; + out[1][2] = in1[1][0] * in2[0][2] + in1[1][1] * in2[1][2] + + in1[1][2] * in2[2][2]; + out[1][3] = in1[1][0] * in2[0][3] + in1[1][1] * in2[1][3] + + in1[1][2] * in2[2][3] + in1[1][3]; + out[2][0] = in1[2][0] * in2[0][0] + in1[2][1] * in2[1][0] + + in1[2][2] * in2[2][0]; + out[2][1] = in1[2][0] * in2[0][1] + in1[2][1] * in2[1][1] + + in1[2][2] * in2[2][1]; + out[2][2] = in1[2][0] * in2[0][2] + in1[2][1] * in2[1][2] + + in1[2][2] * in2[2][2]; + out[2][3] = in1[2][0] * in2[0][3] + in1[2][1] * in2[1][3] + + in1[2][2] * in2[2][3] + in1[2][3]; +} + + +//============================================================================ + + +float Q_fabs (float f) +{ +#if 0 + if (f >= 0) + return f; + return -f; +#else + int tmp = * ( int * ) &f; + tmp &= 0x7FFFFFFF; + return * ( float * ) &tmp; +#endif +} + +#if defined _M_IX86 && !defined C_ONLY +#pragma warning (disable:4035) +__declspec( naked ) long Q_ftol( float f ) +{ + static int tmp; + __asm fld dword ptr [esp+4] + __asm fistp tmp + __asm mov eax, tmp + __asm ret +} +#pragma warning (default:4035) +#endif + +/* +=============== +LerpAngle + +=============== +*/ +float LerpAngle (float a2, float a1, float frac) +{ + if (a1 - a2 > 180) + a1 -= 360; + if (a1 - a2 < -180) + a1 += 360; + return a2 + frac * (a1 - a2); +} + + +float anglemod(float a) +{ +#if 0 + if (a >= 0) + a -= 360*(int)(a/360); + else + a += 360*( 1 + (int)(-a/360) ); +#endif + a = (360.0/65536) * ((int)(a*(65536/360.0)) & 65535); + return a; +} + + int i; + vec3_t corners[2]; + + +// this is the slow, general version +int BoxOnPlaneSide2 (vec3_t emins, vec3_t emaxs, struct cplane_s *p) +{ + int i; + float dist1, dist2; + int sides; + vec3_t corners[2]; + + for (i=0 ; i<3 ; i++) + { + if (p->normal[i] < 0) + { + corners[0][i] = emins[i]; + corners[1][i] = emaxs[i]; + } + else + { + corners[1][i] = emins[i]; + corners[0][i] = emaxs[i]; + } + } + dist1 = DotProduct (p->normal, corners[0]) - p->dist; + dist2 = DotProduct (p->normal, corners[1]) - p->dist; + sides = 0; + if (dist1 >= 0) + sides = 1; + if (dist2 < 0) + sides |= 2; + + return sides; +} + +/* +================== +BoxOnPlaneSide + +Returns 1, 2, or 1 + 2 +================== +*/ +#if !id386 +int BoxOnPlaneSide (vec3_t emins, vec3_t emaxs, struct cplane_s *p) +{ + float dist1, dist2; + int sides; + +// fast axial cases + if (p->type < 3) + { + if (p->dist <= emins[p->type]) + return 1; + if (p->dist >= emaxs[p->type]) + return 2; + return 3; + } + +// general case + switch (p->signbits) + { + case 0: +dist1 = p->normal[0]*emaxs[0] + p->normal[1]*emaxs[1] + p->normal[2]*emaxs[2]; +dist2 = p->normal[0]*emins[0] + p->normal[1]*emins[1] + p->normal[2]*emins[2]; + break; + case 1: +dist1 = p->normal[0]*emins[0] + p->normal[1]*emaxs[1] + p->normal[2]*emaxs[2]; +dist2 = p->normal[0]*emaxs[0] + p->normal[1]*emins[1] + p->normal[2]*emins[2]; + break; + case 2: +dist1 = p->normal[0]*emaxs[0] + p->normal[1]*emins[1] + p->normal[2]*emaxs[2]; +dist2 = p->normal[0]*emins[0] + p->normal[1]*emaxs[1] + p->normal[2]*emins[2]; + break; + case 3: +dist1 = p->normal[0]*emins[0] + p->normal[1]*emins[1] + p->normal[2]*emaxs[2]; +dist2 = p->normal[0]*emaxs[0] + p->normal[1]*emaxs[1] + p->normal[2]*emins[2]; + break; + case 4: +dist1 = p->normal[0]*emaxs[0] + p->normal[1]*emaxs[1] + p->normal[2]*emins[2]; +dist2 = p->normal[0]*emins[0] + p->normal[1]*emins[1] + p->normal[2]*emaxs[2]; + break; + case 5: +dist1 = p->normal[0]*emins[0] + p->normal[1]*emaxs[1] + p->normal[2]*emins[2]; +dist2 = p->normal[0]*emaxs[0] + p->normal[1]*emins[1] + p->normal[2]*emaxs[2]; + break; + case 6: +dist1 = p->normal[0]*emaxs[0] + p->normal[1]*emins[1] + p->normal[2]*emins[2]; +dist2 = p->normal[0]*emins[0] + p->normal[1]*emaxs[1] + p->normal[2]*emaxs[2]; + break; + case 7: +dist1 = p->normal[0]*emins[0] + p->normal[1]*emins[1] + p->normal[2]*emins[2]; +dist2 = p->normal[0]*emaxs[0] + p->normal[1]*emaxs[1] + p->normal[2]*emaxs[2]; + break; + default: + dist1 = dist2 = 0; // shut up compiler + assert( 0 ); + break; + } + + sides = 0; + if (dist1 >= p->dist) + sides = 1; + if (dist2 < p->dist) + sides |= 2; + + assert( sides != 0 ); + + return sides; +} +#else +#pragma warning( disable: 4035 ) + +__declspec( naked ) int BoxOnPlaneSide (vec3_t emins, vec3_t emaxs, struct cplane_s *p) +{ + static int bops_initialized; + static int Ljmptab[8]; + + __asm { + + push ebx + + cmp bops_initialized, 1 + je initialized + mov bops_initialized, 1 + + mov Ljmptab[0*4], offset Lcase0 + mov Ljmptab[1*4], offset Lcase1 + mov Ljmptab[2*4], offset Lcase2 + mov Ljmptab[3*4], offset Lcase3 + mov Ljmptab[4*4], offset Lcase4 + mov Ljmptab[5*4], offset Lcase5 + mov Ljmptab[6*4], offset Lcase6 + mov Ljmptab[7*4], offset Lcase7 + +initialized: + + mov edx,ds:dword ptr[4+12+esp] + mov ecx,ds:dword ptr[4+4+esp] + xor eax,eax + mov ebx,ds:dword ptr[4+8+esp] + mov al,ds:byte ptr[17+edx] + cmp al,8 + jge Lerror + fld ds:dword ptr[0+edx] + fld st(0) + jmp dword ptr[Ljmptab+eax*4] +Lcase0: + fmul ds:dword ptr[ebx] + fld ds:dword ptr[0+4+edx] + fxch st(2) + fmul ds:dword ptr[ecx] + fxch st(2) + fld st(0) + fmul ds:dword ptr[4+ebx] + fld ds:dword ptr[0+8+edx] + fxch st(2) + fmul ds:dword ptr[4+ecx] + fxch st(2) + fld st(0) + fmul ds:dword ptr[8+ebx] + fxch st(5) + faddp st(3),st(0) + fmul ds:dword ptr[8+ecx] + fxch st(1) + faddp st(3),st(0) + fxch st(3) + faddp st(2),st(0) + jmp LSetSides +Lcase1: + fmul ds:dword ptr[ecx] + fld ds:dword ptr[0+4+edx] + fxch st(2) + fmul ds:dword ptr[ebx] + fxch st(2) + fld st(0) + fmul ds:dword ptr[4+ebx] + fld ds:dword ptr[0+8+edx] + fxch st(2) + fmul ds:dword ptr[4+ecx] + fxch st(2) + fld st(0) + fmul ds:dword ptr[8+ebx] + fxch st(5) + faddp st(3),st(0) + fmul ds:dword ptr[8+ecx] + fxch st(1) + faddp st(3),st(0) + fxch st(3) + faddp st(2),st(0) + jmp LSetSides +Lcase2: + fmul ds:dword ptr[ebx] + fld ds:dword ptr[0+4+edx] + fxch st(2) + fmul ds:dword ptr[ecx] + fxch st(2) + fld st(0) + fmul ds:dword ptr[4+ecx] + fld ds:dword ptr[0+8+edx] + fxch st(2) + fmul ds:dword ptr[4+ebx] + fxch st(2) + fld st(0) + fmul ds:dword ptr[8+ebx] + fxch st(5) + faddp st(3),st(0) + fmul ds:dword ptr[8+ecx] + fxch st(1) + faddp st(3),st(0) + fxch st(3) + faddp st(2),st(0) + jmp LSetSides +Lcase3: + fmul ds:dword ptr[ecx] + fld ds:dword ptr[0+4+edx] + fxch st(2) + fmul ds:dword ptr[ebx] + fxch st(2) + fld st(0) + fmul ds:dword ptr[4+ecx] + fld ds:dword ptr[0+8+edx] + fxch st(2) + fmul ds:dword ptr[4+ebx] + fxch st(2) + fld st(0) + fmul ds:dword ptr[8+ebx] + fxch st(5) + faddp st(3),st(0) + fmul ds:dword ptr[8+ecx] + fxch st(1) + faddp st(3),st(0) + fxch st(3) + faddp st(2),st(0) + jmp LSetSides +Lcase4: + fmul ds:dword ptr[ebx] + fld ds:dword ptr[0+4+edx] + fxch st(2) + fmul ds:dword ptr[ecx] + fxch st(2) + fld st(0) + fmul ds:dword ptr[4+ebx] + fld ds:dword ptr[0+8+edx] + fxch st(2) + fmul ds:dword ptr[4+ecx] + fxch st(2) + fld st(0) + fmul ds:dword ptr[8+ecx] + fxch st(5) + faddp st(3),st(0) + fmul ds:dword ptr[8+ebx] + fxch st(1) + faddp st(3),st(0) + fxch st(3) + faddp st(2),st(0) + jmp LSetSides +Lcase5: + fmul ds:dword ptr[ecx] + fld ds:dword ptr[0+4+edx] + fxch st(2) + fmul ds:dword ptr[ebx] + fxch st(2) + fld st(0) + fmul ds:dword ptr[4+ebx] + fld ds:dword ptr[0+8+edx] + fxch st(2) + fmul ds:dword ptr[4+ecx] + fxch st(2) + fld st(0) + fmul ds:dword ptr[8+ecx] + fxch st(5) + faddp st(3),st(0) + fmul ds:dword ptr[8+ebx] + fxch st(1) + faddp st(3),st(0) + fxch st(3) + faddp st(2),st(0) + jmp LSetSides +Lcase6: + fmul ds:dword ptr[ebx] + fld ds:dword ptr[0+4+edx] + fxch st(2) + fmul ds:dword ptr[ecx] + fxch st(2) + fld st(0) + fmul ds:dword ptr[4+ecx] + fld ds:dword ptr[0+8+edx] + fxch st(2) + fmul ds:dword ptr[4+ebx] + fxch st(2) + fld st(0) + fmul ds:dword ptr[8+ecx] + fxch st(5) + faddp st(3),st(0) + fmul ds:dword ptr[8+ebx] + fxch st(1) + faddp st(3),st(0) + fxch st(3) + faddp st(2),st(0) + jmp LSetSides +Lcase7: + fmul ds:dword ptr[ecx] + fld ds:dword ptr[0+4+edx] + fxch st(2) + fmul ds:dword ptr[ebx] + fxch st(2) + fld st(0) + fmul ds:dword ptr[4+ecx] + fld ds:dword ptr[0+8+edx] + fxch st(2) + fmul ds:dword ptr[4+ebx] + fxch st(2) + fld st(0) + fmul ds:dword ptr[8+ecx] + fxch st(5) + faddp st(3),st(0) + fmul ds:dword ptr[8+ebx] + fxch st(1) + faddp st(3),st(0) + fxch st(3) + faddp st(2),st(0) +LSetSides: + faddp st(2),st(0) + fcomp ds:dword ptr[12+edx] + xor ecx,ecx + fnstsw ax + fcomp ds:dword ptr[12+edx] + and ah,1 + xor ah,1 + add cl,ah + fnstsw ax + and ah,1 + add ah,ah + add cl,ah + pop ebx + mov eax,ecx + ret +Lerror: + int 3 + } +} +#pragma warning( default: 4035 ) +#endif + +void ClearBounds (vec3_t mins, vec3_t maxs) +{ + mins[0] = mins[1] = mins[2] = 99999; + maxs[0] = maxs[1] = maxs[2] = -99999; +} + +void AddPointToBounds (vec3_t v, vec3_t mins, vec3_t maxs) +{ + int i; + vec_t val; + + for (i=0 ; i<3 ; i++) + { + val = v[i]; + if (val < mins[i]) + mins[i] = val; + if (val > maxs[i]) + maxs[i] = val; + } +} + + +int VectorCompare (vec3_t v1, vec3_t v2) +{ + if (v1[0] != v2[0] || v1[1] != v2[1] || v1[2] != v2[2]) + return 0; + + return 1; +} + + +vec_t VectorNormalize (vec3_t v) +{ + float length, ilength; + + length = v[0]*v[0] + v[1]*v[1] + v[2]*v[2]; + length = sqrt (length); // FIXME + + if (length) + { + ilength = 1/length; + v[0] *= ilength; + v[1] *= ilength; + v[2] *= ilength; + } + + return length; + +} + +vec_t VectorNormalize2 (vec3_t v, vec3_t out) +{ + float length, ilength; + + length = v[0]*v[0] + v[1]*v[1] + v[2]*v[2]; + length = sqrt (length); // FIXME + + if (length) + { + ilength = 1/length; + out[0] = v[0]*ilength; + out[1] = v[1]*ilength; + out[2] = v[2]*ilength; + } + + return length; + +} + +void VectorMA (vec3_t veca, float scale, vec3_t vecb, vec3_t vecc) +{ + vecc[0] = veca[0] + scale*vecb[0]; + vecc[1] = veca[1] + scale*vecb[1]; + vecc[2] = veca[2] + scale*vecb[2]; +} + + +vec_t _DotProduct (vec3_t v1, vec3_t v2) +{ + return v1[0]*v2[0] + v1[1]*v2[1] + v1[2]*v2[2]; +} + +void _VectorSubtract (vec3_t veca, vec3_t vecb, vec3_t out) +{ + out[0] = veca[0]-vecb[0]; + out[1] = veca[1]-vecb[1]; + out[2] = veca[2]-vecb[2]; +} + +void _VectorAdd (vec3_t veca, vec3_t vecb, vec3_t out) +{ + out[0] = veca[0]+vecb[0]; + out[1] = veca[1]+vecb[1]; + out[2] = veca[2]+vecb[2]; +} + +void _VectorCopy (vec3_t in, vec3_t out) +{ + out[0] = in[0]; + out[1] = in[1]; + out[2] = in[2]; +} + +void CrossProduct (vec3_t v1, vec3_t v2, vec3_t cross) +{ + cross[0] = v1[1]*v2[2] - v1[2]*v2[1]; + cross[1] = v1[2]*v2[0] - v1[0]*v2[2]; + cross[2] = v1[0]*v2[1] - v1[1]*v2[0]; +} + +double sqrt(double x); + +vec_t VectorLength(vec3_t v) +{ + int i; + float length; + + length = 0; + for (i=0 ; i< 3 ; i++) + length += v[i]*v[i]; + length = sqrt (length); // FIXME + + return length; +} + +void VectorInverse (vec3_t v) +{ + v[0] = -v[0]; + v[1] = -v[1]; + v[2] = -v[2]; +} + +void VectorScale (vec3_t in, vec_t scale, vec3_t out) +{ + out[0] = in[0]*scale; + out[1] = in[1]*scale; + out[2] = in[2]*scale; +} + + +int Q_log2(int val) +{ + int answer=0; + while (val>>=1) + answer++; + return answer; +} + + + +//==================================================================================== + +/* +============ +COM_SkipPath +============ +*/ +char *COM_SkipPath (char *pathname) +{ + char *last; + + last = pathname; + while (*pathname) + { + if (*pathname=='/') + last = pathname+1; + pathname++; + } + return last; +} + +/* +============ +COM_StripExtension +============ +*/ +void COM_StripExtension (char *in, char *out) +{ + while (*in && *in != '.') + *out++ = *in++; + *out = 0; +} + +/* +============ +COM_FileExtension +============ +*/ +char *COM_FileExtension (char *in) +{ + static char exten[8]; + int i; + + while (*in && *in != '.') + in++; + if (!*in) + return ""; + in++; + for (i=0 ; i<7 && *in ; i++,in++) + exten[i] = *in; + exten[i] = 0; + return exten; +} + +/* +============ +COM_FileBase +============ +*/ +void COM_FileBase (char *in, char *out) +{ + char *s, *s2; + + s = in + strlen(in) - 1; + + while (s != in && *s != '.') + s--; + + for (s2 = s ; s2 != in && *s2 != '/' ; s2--) + ; + + if (s-s2 < 2) + out[0] = 0; + else + { + s--; + strncpy (out,s2+1, s-s2); + out[s-s2] = 0; + } +} + +/* +============ +COM_FilePath + +Returns the path up to, but not including the last / +============ +*/ +void COM_FilePath (char *in, char *out) +{ + char *s; + + s = in + strlen(in) - 1; + + while (s != in && *s != '/') + s--; + + strncpy (out,in, s-in); + out[s-in] = 0; +} + + +/* +================== +COM_DefaultExtension +================== +*/ +void COM_DefaultExtension (char *path, char *extension) +{ + char *src; +// +// if path doesn't have a .EXT, append extension +// (extension should include the .) +// + src = path + strlen(path) - 1; + + while (*src != '/' && src != path) + { + if (*src == '.') + return; // it has an extension + src--; + } + + strcat (path, extension); +} + +/* +============================================================================ + + BYTE ORDER FUNCTIONS + +============================================================================ +*/ + +qboolean bigendien; + +// can't just use function pointers, or dll linkage can +// mess up when qcommon is included in multiple places +short (*_BigShort) (short l); +short (*_LittleShort) (short l); +int (*_BigLong) (int l); +int (*_LittleLong) (int l); +float (*_BigFloat) (float l); +float (*_LittleFloat) (float l); + +short BigShort(short l){return _BigShort(l);} +short LittleShort(short l) {return _LittleShort(l);} +int BigLong (int l) {return _BigLong(l);} +int LittleLong (int l) {return _LittleLong(l);} +float BigFloat (float l) {return _BigFloat(l);} +float LittleFloat (float l) {return _LittleFloat(l);} + +short ShortSwap (short l) +{ + byte b1,b2; + + b1 = l&255; + b2 = (l>>8)&255; + + return (b1<<8) + b2; +} + +short ShortNoSwap (short l) +{ + return l; +} + +int LongSwap (int l) +{ + byte b1,b2,b3,b4; + + b1 = l&255; + b2 = (l>>8)&255; + b3 = (l>>16)&255; + b4 = (l>>24)&255; + + return ((int)b1<<24) + ((int)b2<<16) + ((int)b3<<8) + b4; +} + +int LongNoSwap (int l) +{ + return l; +} + +float FloatSwap (float f) +{ + union + { + float f; + byte b[4]; + } dat1, dat2; + + + dat1.f = f; + dat2.b[0] = dat1.b[3]; + dat2.b[1] = dat1.b[2]; + dat2.b[2] = dat1.b[1]; + dat2.b[3] = dat1.b[0]; + return dat2.f; +} + +float FloatNoSwap (float f) +{ + return f; +} + +/* +================ +Swap_Init +================ +*/ +void Swap_Init (void) +{ + byte swaptest[2] = {1,0}; + +// set the byte swapping variables in a portable manner + if ( *(short *)swaptest == 1) + { + bigendien = false; + _BigShort = ShortSwap; + _LittleShort = ShortNoSwap; + _BigLong = LongSwap; + _LittleLong = LongNoSwap; + _BigFloat = FloatSwap; + _LittleFloat = FloatNoSwap; + } + else + { + bigendien = true; + _BigShort = ShortNoSwap; + _LittleShort = ShortSwap; + _BigLong = LongNoSwap; + _LittleLong = LongSwap; + _BigFloat = FloatNoSwap; + _LittleFloat = FloatSwap; + } + +} + + + +/* +============ +va + +does a varargs printf into a temp buffer, so I don't need to have +varargs versions of all text functions. +FIXME: make this buffer size safe someday +============ +*/ +char *va(char *format, ...) +{ + va_list argptr; + static char string[1024]; + + va_start (argptr, format); + vsprintf (string, format,argptr); + va_end (argptr); + + return string; +} + + +char com_token[MAX_TOKEN_CHARS]; + +/* +============== +COM_Parse + +Parse a token out of a string +============== +*/ +char *COM_Parse (char **data_p) +{ + int c; + int len; + char *data; + + data = *data_p; + len = 0; + com_token[0] = 0; + + if (!data) + { + *data_p = NULL; + return ""; + } + +// skip whitespace +skipwhite: + while ( (c = *data) <= ' ') + { + if (c == 0) + { + *data_p = NULL; + return ""; + } + data++; + } + +// skip // comments + if (c=='/' && data[1] == '/') + { + while (*data && *data != '\n') + data++; + goto skipwhite; + } + + +// handle quoted strings specially + if (c == '\"') + { + data++; + while (1) + { + c = *data++; + if (c=='\"' || !c) + { + com_token[len] = 0; + *data_p = data; + return com_token; + } + if (len < MAX_TOKEN_CHARS) + { + com_token[len] = c; + len++; + } + } + } + +// parse a regular word + do + { + if (len < MAX_TOKEN_CHARS) + { + com_token[len] = c; + len++; + } + data++; + c = *data; + } while (c>32); + + if (len == MAX_TOKEN_CHARS) + { +// Com_Printf ("Token exceeded %i chars, discarded.\n", MAX_TOKEN_CHARS); + len = 0; + } + com_token[len] = 0; + + *data_p = data; + return com_token; +} + + +/* +=============== +Com_PageInMemory + +=============== +*/ +int paged_total; + +void Com_PageInMemory (byte *buffer, int size) +{ + int i; + + for (i=size-1 ; i>0 ; i-=4096) + paged_total += buffer[i]; +} + + + +/* +============================================================================ + + LIBRARY REPLACEMENT FUNCTIONS + +============================================================================ +*/ + +// FIXME: replace all Q_stricmp with Q_strcasecmp +int Q_stricmp (char *s1, char *s2) +{ +#if defined(WIN32) + return _stricmp (s1, s2); +#else + return strcasecmp (s1, s2); +#endif +} + + +int Q_strncasecmp (char *s1, char *s2, int n) +{ + int c1, c2; + + do + { + c1 = *s1++; + c2 = *s2++; + + if (!n--) + return 0; // strings are equal until end point + + if (c1 != c2) + { + if (c1 >= 'a' && c1 <= 'z') + c1 -= ('a' - 'A'); + if (c2 >= 'a' && c2 <= 'z') + c2 -= ('a' - 'A'); + if (c1 != c2) + return -1; // strings not equal + } + } while (c1); + + return 0; // strings are equal +} + +int Q_strcasecmp (char *s1, char *s2) +{ + return Q_strncasecmp (s1, s2, 99999); +} + + + +void Com_sprintf (char *dest, int size, char *fmt, ...) +{ + int len; + va_list argptr; + char bigbuffer[0x10000]; + + va_start (argptr,fmt); + len = vsprintf (bigbuffer,fmt,argptr); + va_end (argptr); + if (len >= size) + Com_Printf ("Com_sprintf: overflow of %i in %i\n", len, size); + strncpy (dest, bigbuffer, size-1); +} + +/* +===================================================================== + + INFO STRINGS + +===================================================================== +*/ + +/* +=============== +Info_ValueForKey + +Searches the string for the given +key and returns the associated value, or an empty string. +=============== +*/ +char *Info_ValueForKey (char *s, char *key) +{ + char pkey[512]; + static char value[2][512]; // use two buffers so compares + // work without stomping on each other + static int valueindex; + char *o; + + valueindex ^= 1; + if (*s == '\\') + s++; + while (1) + { + o = pkey; + while (*s != '\\') + { + if (!*s) + return ""; + *o++ = *s++; + } + *o = 0; + s++; + + o = value[valueindex]; + + while (*s != '\\' && *s) + { + if (!*s) + return ""; + *o++ = *s++; + } + *o = 0; + + if (!strcmp (key, pkey) ) + return value[valueindex]; + + if (!*s) + return ""; + s++; + } +} + +void Info_RemoveKey (char *s, char *key) +{ + char *start; + char pkey[512]; + char value[512]; + char *o; + + if (strstr (key, "\\")) + { +// Com_Printf ("Can't use a key with a \\\n"); + return; + } + + while (1) + { + start = s; + if (*s == '\\') + s++; + o = pkey; + while (*s != '\\') + { + if (!*s) + return; + *o++ = *s++; + } + *o = 0; + s++; + + o = value; + while (*s != '\\' && *s) + { + if (!*s) + return; + *o++ = *s++; + } + *o = 0; + + if (!strcmp (key, pkey) ) + { + strcpy (start, s); // remove this part + return; + } + + if (!*s) + return; + } + +} + + +/* +================== +Info_Validate + +Some characters are illegal in info strings because they +can mess up the server's parsing +================== +*/ +qboolean Info_Validate (char *s) +{ + if (strstr (s, "\"")) + return false; + if (strstr (s, ";")) + return false; + return true; +} + +void Info_SetValueForKey (char *s, char *key, char *value) +{ + char newi[MAX_INFO_STRING], *v; + int c; + int maxsize = MAX_INFO_STRING; + + if (strstr (key, "\\") || strstr (value, "\\") ) + { + Com_Printf ("Can't use keys or values with a \\\n"); + return; + } + + if (strstr (key, ";") ) + { + Com_Printf ("Can't use keys or values with a semicolon\n"); + return; + } + + if (strstr (key, "\"") || strstr (value, "\"") ) + { + Com_Printf ("Can't use keys or values with a \"\n"); + return; + } + + if (strlen(key) > MAX_INFO_KEY-1 || strlen(value) > MAX_INFO_KEY-1) + { + Com_Printf ("Keys and values must be < 64 characters.\n"); + return; + } + Info_RemoveKey (s, key); + if (!value || !strlen(value)) + return; + + Com_sprintf (newi, sizeof(newi), "\\%s\\%s", key, value); + + if (strlen(newi) + strlen(s) > maxsize) + { + Com_Printf ("Info string length exceeded\n"); + return; + } + + // only copy ascii values + s += strlen(s); + v = newi; + while (*v) + { + c = *v++; + c &= 127; // strip high bits + if (c >= 32 && c < 127) + *s++ = c; + } + *s = 0; +} + +//==================================================================== + + diff --git a/src/q_shared.h b/src/q_shared.h new file mode 100644 index 0000000..5b0ed67 --- /dev/null +++ b/src/q_shared.h @@ -0,0 +1,1159 @@ + +// q_shared.h -- included first by ALL program modules + +#ifndef Q_SHARED +#define Q_SHARED + +#ifdef _WIN32 +// unknown pragmas are SUPPOSED to be ignored, but.... +#pragma warning(disable : 4244) // MIPS +#pragma warning(disable : 4136) // X86 +#pragma warning(disable : 4051) // ALPHA + +#pragma warning(disable : 4018) // signed/unsigned mismatch +#pragma warning(disable : 4305) // truncation from const double to float + +#endif + +#include +#include +#include +#include +#include +#include +#include + +#if defined _M_IX86 && !defined C_ONLY +#define id386 1 +#else +#define id386 0 +#endif + +#if defined _M_ALPHA && !defined C_ONLY +#define idaxp 1 +#else +#define idaxp 0 +#endif + +typedef unsigned char byte; +typedef enum {false, true} qboolean; + + +#ifndef NULL +#define NULL ((void *)0) +#endif + +// angle indexes +#define PITCH 0 // up / down +#define YAW 1 // left / right +#define ROLL 2 // fall over + +#define MAX_STRING_CHARS 1024 // max length of a string passed to Cmd_TokenizeString +#define MAX_STRING_TOKENS 80 // max tokens resulting from Cmd_TokenizeString +#define MAX_TOKEN_CHARS 128 // max length of an individual token + +#define MAX_QPATH 64 // max length of a quake game pathname +#define MAX_OSPATH 128 // max length of a filesystem pathname + +// +// per-level limits +// +#define MAX_CLIENTS 256 // absolute limit +#define MAX_EDICTS 1024 // must change protocol to increase more +#define MAX_LIGHTSTYLES 256 +#define MAX_MODELS 256 // these are sent over the net as bytes +#define MAX_SOUNDS 256 // so they cannot be blindly increased +#define MAX_IMAGES 256 +#define MAX_ITEMS 256 +#define MAX_GENERAL (MAX_CLIENTS*2) // general config strings + + +// game print flags +#define PRINT_LOW 0 // pickup messages +#define PRINT_MEDIUM 1 // death messages +#define PRINT_HIGH 2 // critical messages +#define PRINT_CHAT 3 // chat messages + + + +#define ERR_FATAL 0 // exit the entire game with a popup window +#define ERR_DROP 1 // print to console and disconnect from game +#define ERR_DISCONNECT 2 // don't kill server + +#define PRINT_ALL 0 +#define PRINT_DEVELOPER 1 // only print when "developer 1" +#define PRINT_ALERT 2 + + +// destination class for gi.multicast() +typedef enum +{ +MULTICAST_ALL, +MULTICAST_PHS, +MULTICAST_PVS, +MULTICAST_ALL_R, +MULTICAST_PHS_R, +MULTICAST_PVS_R +} multicast_t; + + +/* +============================================================== + +MATHLIB + +============================================================== +*/ + +typedef float vec_t; +typedef vec_t vec3_t[3]; +typedef vec_t vec5_t[5]; + +typedef int fixed4_t; +typedef int fixed8_t; +typedef int fixed16_t; + +#ifndef M_PI +#define M_PI 3.14159265358979323846 // matches value in gcc v2 math.h +#endif + +struct cplane_s; + +extern vec3_t vec3_origin; + +#define nanmask (255<<23) + +#define IS_NAN(x) (((*(int *)&x)&nanmask)==nanmask) + +// microsoft's fabs seems to be ungodly slow... +//float Q_fabs (float f); +//#define fabs(f) Q_fabs(f) +#if !defined C_ONLY +extern long Q_ftol( float f ); +#else +#define Q_ftol( f ) ( long ) (f) +#endif + +#define DotProduct(x,y) (x[0]*y[0]+x[1]*y[1]+x[2]*y[2]) +#define VectorSubtract(a,b,c) (c[0]=a[0]-b[0],c[1]=a[1]-b[1],c[2]=a[2]-b[2]) +#define VectorAdd(a,b,c) (c[0]=a[0]+b[0],c[1]=a[1]+b[1],c[2]=a[2]+b[2]) +#define VectorCopy(a,b) (b[0]=a[0],b[1]=a[1],b[2]=a[2]) +#define VectorClear(a) (a[0]=a[1]=a[2]=0) +#define VectorNegate(a,b) (b[0]=-a[0],b[1]=-a[1],b[2]=-a[2]) +#define VectorSet(v, x, y, z) (v[0]=(x), v[1]=(y), v[2]=(z)) + +void VectorMA (vec3_t veca, float scale, vec3_t vecb, vec3_t vecc); + +// just in case you do't want to use the macros +vec_t _DotProduct (vec3_t v1, vec3_t v2); +void _VectorSubtract (vec3_t veca, vec3_t vecb, vec3_t out); +void _VectorAdd (vec3_t veca, vec3_t vecb, vec3_t out); +void _VectorCopy (vec3_t in, vec3_t out); + +void ClearBounds (vec3_t mins, vec3_t maxs); +void AddPointToBounds (vec3_t v, vec3_t mins, vec3_t maxs); +int VectorCompare (vec3_t v1, vec3_t v2); +vec_t VectorLength (vec3_t v); +void CrossProduct (vec3_t v1, vec3_t v2, vec3_t cross); +vec_t VectorNormalize (vec3_t v); // returns vector length +vec_t VectorNormalize2 (vec3_t v, vec3_t out); +void VectorInverse (vec3_t v); +void VectorScale (vec3_t in, vec_t scale, vec3_t out); +int Q_log2(int val); + +void R_ConcatRotations (float in1[3][3], float in2[3][3], float out[3][3]); +void R_ConcatTransforms (float in1[3][4], float in2[3][4], float out[3][4]); + +void AngleVectors (vec3_t angles, vec3_t forward, vec3_t right, vec3_t up); +int BoxOnPlaneSide (vec3_t emins, vec3_t emaxs, struct cplane_s *plane); +float anglemod(float a); +float LerpAngle (float a1, float a2, float frac); + +#define BOX_ON_PLANE_SIDE(emins, emaxs, p) \ + (((p)->type < 3)? \ + ( \ + ((p)->dist <= (emins)[(p)->type])? \ + 1 \ + : \ + ( \ + ((p)->dist >= (emaxs)[(p)->type])?\ + 2 \ + : \ + 3 \ + ) \ + ) \ + : \ + BoxOnPlaneSide( (emins), (emaxs), (p))) + +void ProjectPointOnPlane( vec3_t dst, const vec3_t p, const vec3_t normal ); +void PerpendicularVector( vec3_t dst, const vec3_t src ); +void RotatePointAroundVector( vec3_t dst, const vec3_t dir, const vec3_t point, float degrees ); + + +//============================================= + +char *COM_SkipPath (char *pathname); +void COM_StripExtension (char *in, char *out); +void COM_FileBase (char *in, char *out); +void COM_FilePath (char *in, char *out); +void COM_DefaultExtension (char *path, char *extension); + +char *COM_Parse (char **data_p); +// data is an in/out parm, returns a parsed out token + +void Com_sprintf (char *dest, int size, char *fmt, ...); + +void Com_PageInMemory (byte *buffer, int size); + +//============================================= + +// portable case insensitive compare +int Q_stricmp (char *s1, char *s2); +int Q_strcasecmp (char *s1, char *s2); +int Q_strncasecmp (char *s1, char *s2, int n); + +//============================================= + +short BigShort(short l); +short LittleShort(short l); +int BigLong (int l); +int LittleLong (int l); +float BigFloat (float l); +float LittleFloat (float l); + +void Swap_Init (void); +char *va(char *format, ...); + +//============================================= + +// +// key / value info strings +// +#define MAX_INFO_KEY 64 +#define MAX_INFO_VALUE 64 +#define MAX_INFO_STRING 512 + +char *Info_ValueForKey (char *s, char *key); +void Info_RemoveKey (char *s, char *key); +void Info_SetValueForKey (char *s, char *key, char *value); +qboolean Info_Validate (char *s); + +/* +============================================================== + +SYSTEM SPECIFIC + +============================================================== +*/ + +extern int curtime; // time returned by last Sys_Milliseconds + +int Sys_Milliseconds (void); +void Sys_Mkdir (char *path); + +// large block stack allocation routines +void *Hunk_Begin (int maxsize); +void *Hunk_Alloc (int size); +void Hunk_Free (void *buf); +int Hunk_End (void); + +// directory searching +#define SFF_ARCH 0x01 +#define SFF_HIDDEN 0x02 +#define SFF_RDONLY 0x04 +#define SFF_SUBDIR 0x08 +#define SFF_SYSTEM 0x10 + +/* +** pass in an attribute mask of things you wish to REJECT +*/ +char *Sys_FindFirst (char *path, unsigned musthave, unsigned canthave ); +char *Sys_FindNext ( unsigned musthave, unsigned canthave ); +void Sys_FindClose (void); + + +// this is only here so the functions in q_shared.c and q_shwin.c can link +void Sys_Error (char *error, ...); +void Com_Printf (char *msg, ...); + + +/* +========================================================== + +CVARS (console variables) + +========================================================== +*/ + +#ifndef CVAR +#define CVAR + +#define CVAR_ARCHIVE 1 // set to cause it to be saved to vars.rc +#define CVAR_USERINFO 2 // added to userinfo when changed +#define CVAR_SERVERINFO 4 // added to serverinfo when changed +#define CVAR_NOSET 8 // don't allow change from console at all, + // but can be set from the command line +#define CVAR_LATCH 16 // save changes until server restart + +// nothing outside the Cvar_*() functions should modify these fields! +typedef struct cvar_s +{ + char *name; + char *string; + char *latched_string; // for CVAR_LATCH vars + int flags; + qboolean modified; // set each time the cvar is changed + float value; + struct cvar_s *next; +} cvar_t; + +#endif // CVAR + +/* +============================================================== + +COLLISION DETECTION + +============================================================== +*/ + +// lower bits are stronger, and will eat weaker brushes completely +#define CONTENTS_SOLID 1 // an eye is never valid in a solid +#define CONTENTS_WINDOW 2 // translucent, but not watery +#define CONTENTS_AUX 4 +#define CONTENTS_LAVA 8 +#define CONTENTS_SLIME 16 +#define CONTENTS_WATER 32 +#define CONTENTS_MIST 64 +#define LAST_VISIBLE_CONTENTS 64 + +// remaining contents are non-visible, and don't eat brushes + +#define CONTENTS_AREAPORTAL 0x8000 + +#define CONTENTS_PLAYERCLIP 0x10000 +#define CONTENTS_MONSTERCLIP 0x20000 + +// currents can be added to any other contents, and may be mixed +#define CONTENTS_CURRENT_0 0x40000 +#define CONTENTS_CURRENT_90 0x80000 +#define CONTENTS_CURRENT_180 0x100000 +#define CONTENTS_CURRENT_270 0x200000 +#define CONTENTS_CURRENT_UP 0x400000 +#define CONTENTS_CURRENT_DOWN 0x800000 + +#define CONTENTS_ORIGIN 0x1000000 // removed before bsping an entity + +#define CONTENTS_MONSTER 0x2000000 // should never be on a brush, only in game +#define CONTENTS_DEADMONSTER 0x4000000 +#define CONTENTS_DETAIL 0x8000000 // brushes to be added after vis leafs +#define CONTENTS_TRANSLUCENT 0x10000000 // auto set if any surface has trans +#define CONTENTS_LADDER 0x20000000 + + + +#define SURF_LIGHT 0x1 // value will hold the light strength + +#define SURF_SLICK 0x2 // effects game physics + +#define SURF_SKY 0x4 // don't draw, but add to skybox +#define SURF_WARP 0x8 // turbulent water warp +#define SURF_TRANS33 0x10 +#define SURF_TRANS66 0x20 +#define SURF_FLOWING 0x40 // scroll towards angle +#define SURF_NODRAW 0x80 // don't bother referencing the texture + + + +// content masks +#define MASK_ALL (-1) +#define MASK_SOLID (CONTENTS_SOLID|CONTENTS_WINDOW) +#define MASK_PLAYERSOLID (CONTENTS_SOLID|CONTENTS_PLAYERCLIP|CONTENTS_WINDOW|CONTENTS_MONSTER) +#define MASK_DEADSOLID (CONTENTS_SOLID|CONTENTS_PLAYERCLIP|CONTENTS_WINDOW) +#define MASK_MONSTERSOLID (CONTENTS_SOLID|CONTENTS_MONSTERCLIP|CONTENTS_WINDOW|CONTENTS_MONSTER) +#define MASK_WATER (CONTENTS_WATER|CONTENTS_LAVA|CONTENTS_SLIME) +#define MASK_OPAQUE (CONTENTS_SOLID|CONTENTS_SLIME|CONTENTS_LAVA) +#define MASK_SHOT (CONTENTS_SOLID|CONTENTS_MONSTER|CONTENTS_WINDOW|CONTENTS_DEADMONSTER) +#define MASK_CURRENT (CONTENTS_CURRENT_0|CONTENTS_CURRENT_90|CONTENTS_CURRENT_180|CONTENTS_CURRENT_270|CONTENTS_CURRENT_UP|CONTENTS_CURRENT_DOWN) +#define MASK_BOTSOLID (CONTENTS_SOLID|CONTENTS_LADDER/*CONTENTS_PLAYERCLIP*/|CONTENTS_WINDOW|CONTENTS_MONSTER) +#define MASK_BOTSOLIDX (CONTENTS_SOLID|CONTENTS_WINDOW|CONTENTS_PLAYERCLIP|CONTENTS_MONSTER) +#define MASK_GROUND (CONTENTS_SOLID|CONTENTS_WINDOW|CONTENTS_MONSTER) + +// gi.BoxEdicts() can return a list of either solid or trigger entities +// FIXME: eliminate AREA_ distinction? +#define AREA_SOLID 1 +#define AREA_TRIGGERS 2 + + +// plane_t structure +// !!! if this is changed, it must be changed in asm code too !!! +typedef struct cplane_s +{ + vec3_t normal; + float dist; + byte type; // for fast side tests + byte signbits; // signx + (signy<<1) + (signz<<1) + byte pad[2]; +} cplane_t; + +// structure offset for asm code +#define CPLANE_NORMAL_X 0 +#define CPLANE_NORMAL_Y 4 +#define CPLANE_NORMAL_Z 8 +#define CPLANE_DIST 12 +#define CPLANE_TYPE 16 +#define CPLANE_SIGNBITS 17 +#define CPLANE_PAD0 18 +#define CPLANE_PAD1 19 + +typedef struct cmodel_s +{ + vec3_t mins, maxs; + vec3_t origin; // for sounds or lights + int headnode; +} cmodel_t; + +typedef struct csurface_s +{ + char name[16]; + int flags; + int value; +} csurface_t; + +typedef struct mapsurface_s // used internally due to name len probs //ZOID +{ + csurface_t c; + char rname[32]; +} mapsurface_t; + +// a trace is returned when a box is swept through the world +typedef struct +{ + qboolean allsolid; // if true, plane is not valid + qboolean startsolid; // if true, the initial point was in a solid area + float fraction; // time completed, 1.0 = didn't hit anything + vec3_t endpos; // final position + cplane_t plane; // surface normal at impact + csurface_t *surface; // surface hit + int contents; // contents on other side of surface hit + struct edict_s *ent; // not set by CM_*() functions +} trace_t; + + + +// pmove_state_t is the information necessary for client side movement +// prediction +typedef enum +{ + // can accelerate and turn + PM_NORMAL, + PM_SPECTATOR, + // no acceleration or turning + PM_DEAD, + PM_GIB, // different bounding box + PM_FREEZE +} pmtype_t; + +// pmove->pm_flags +#define PMF_DUCKED 1 +#define PMF_JUMP_HELD 2 +#define PMF_ON_GROUND 4 +#define PMF_TIME_WATERJUMP 8 // pm_time is waterjump +#define PMF_TIME_LAND 16 // pm_time is time before rejump +#define PMF_TIME_TELEPORT 32 // pm_time is non-moving time +#define PMF_NO_PREDICTION 64 // temporarily disables prediction (used for grappling hook) + +// this structure needs to be communicated bit-accurate +// from the server to the client to guarantee that +// prediction stays in sync, so no floats are used. +// if any part of the game code modifies this struct, it +// will result in a prediction error of some degree. +typedef struct +{ + pmtype_t pm_type; + + short origin[3]; // 12.3 + short velocity[3]; // 12.3 + byte pm_flags; // ducked, jump_held, etc + byte pm_time; // each unit = 8 ms + short gravity; + short delta_angles[3]; // add to command angles to get view direction + // changed by spawns, rotating objects, and teleporters +} pmove_state_t; + + +// +// button bits +// +#define BUTTON_ATTACK 1 +#define BUTTON_USE 2 +#define BUTTON_ANY 128 // any key whatsoever + + +// usercmd_t is sent to the server each client frame +typedef struct usercmd_s +{ + byte msec; + byte buttons; + short angles[3]; + short forwardmove, sidemove, upmove; + byte impulse; // remove? + byte lightlevel; // light level the player is standing on +} usercmd_t; + + +#define MAXTOUCH 32 +typedef struct +{ + // state (in / out) + pmove_state_t s; + + // command (in) + usercmd_t cmd; + qboolean snapinitial; // if s has been changed outside pmove + + // results (out) + int numtouch; + struct edict_s *touchents[MAXTOUCH]; + + vec3_t viewangles; // clamped + float viewheight; + + vec3_t mins, maxs; // bounding box size + + struct edict_s *groundentity; + int watertype; + int waterlevel; + + // callbacks to test the world + trace_t (*trace) (vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end); + int (*pointcontents) (vec3_t point); +} pmove_t; + + +// entity_state_t->effects +// Effects are things handled on the client side (lights, particles, frame animations) +// that happen constantly on the given entity. +// An entity that has effects will be sent to the client +// even if it has a zero index model. +#define EF_ROTATE 0x00000001 // rotate (bonus items) +#define EF_GIB 0x00000002 // leave a trail +#define EF_BLASTER 0x00000008 // redlight + trail +#define EF_ROCKET 0x00000010 // redlight + trail +#define EF_GRENADE 0x00000020 +#define EF_HYPERBLASTER 0x00000040 +#define EF_BFG 0x00000080 +#define EF_COLOR_SHELL 0x00000100 +#define EF_POWERSCREEN 0x00000200 +#define EF_ANIM01 0x00000400 // automatically cycle between frames 0 and 1 at 2 hz +#define EF_ANIM23 0x00000800 // automatically cycle between frames 2 and 3 at 2 hz +#define EF_ANIM_ALL 0x00001000 // automatically cycle through all frames at 2hz +#define EF_ANIM_ALLFAST 0x00002000 // automatically cycle through all frames at 10hz +#define EF_FLIES 0x00004000 +#define EF_QUAD 0x00008000 +#define EF_PENT 0x00010000 +#define EF_TELEPORTER 0x00020000 // particle fountain +#define EF_FLAG1 0x00040000 +#define EF_FLAG2 0x00080000 +// RAFAEL +#define EF_IONRIPPER 0x00100000 +#define EF_GREENGIB 0x00200000 +#define EF_BLUEHYPERBLASTER 0x00400000 +#define EF_SPINNINGLIGHTS 0x00800000 +#define EF_PLASMA 0x01000000 +#define EF_TRAP 0x02000000 + +//ROGUE +#define EF_TRACKER 0x04000000 +#define EF_DOUBLE 0x08000000 +#define EF_SPHERETRANS 0x10000000 +#define EF_TAGTRAIL 0x20000000 +#define EF_HALF_DAMAGE 0x40000000 +#define EF_TRACKERTRAIL 0x80000000 +//ROGUE + +// entity_state_t->renderfx flags +#define RF_MINLIGHT 1 // allways have some light (viewmodel) +#define RF_VIEWERMODEL 2 // don't draw through eyes, only mirrors +#define RF_WEAPONMODEL 4 // only draw through eyes +#define RF_FULLBRIGHT 8 // allways draw full intensity +#define RF_DEPTHHACK 16 // for view weapon Z crunching +#define RF_TRANSLUCENT 32 +#define RF_FRAMELERP 64 +#define RF_BEAM 128 +#define RF_CUSTOMSKIN 256 // skin is an index in image_precache +#define RF_GLOW 512 // pulse lighting for bonus items +#define RF_SHELL_RED 1024 +#define RF_SHELL_GREEN 2048 +#define RF_SHELL_BLUE 4096 + +//ROGUE +#define RF_IR_VISIBLE 0x00008000 // 32768 +#define RF_SHELL_DOUBLE 0x00010000 // 65536 +#define RF_SHELL_HALF_DAM 0x00020000 +#define RF_USE_DISGUISE 0x00040000 +//ROGUE + +// player_state_t->refdef flags +#define RDF_UNDERWATER 1 // warp the screen as apropriate +#define RDF_NOWORLDMODEL 2 // used for player configuration screen + +//ROGUE +#define RDF_IRGOGGLES 4 +#define RDF_UVGOGGLES 8 +//ROGUE + +// +// muzzle flashes / player effects +// +#define MZ_BLASTER 0 +#define MZ_MACHINEGUN 1 +#define MZ_SHOTGUN 2 +#define MZ_CHAINGUN1 3 +#define MZ_CHAINGUN2 4 +#define MZ_CHAINGUN3 5 +#define MZ_RAILGUN 6 +#define MZ_ROCKET 7 +#define MZ_GRENADE 8 +#define MZ_LOGIN 9 +#define MZ_LOGOUT 10 +#define MZ_RESPAWN 11 +#define MZ_BFG 12 +#define MZ_SSHOTGUN 13 +#define MZ_HYPERBLASTER 14 +#define MZ_ITEMRESPAWN 15 +// RAFAEL +#define MZ_IONRIPPER 16 +#define MZ_BLUEHYPERBLASTER 17 +#define MZ_PHALANX 18 +#define MZ_SILENCED 128 // bit flag ORed with one of the above numbers + +//ROGUE +#define MZ_ETF_RIFLE 30 +#define MZ_UNUSED 31 +#define MZ_SHOTGUN2 32 +#define MZ_HEATBEAM 33 +#define MZ_BLASTER2 34 +#define MZ_TRACKER 35 +#define MZ_NUKE1 36 +#define MZ_NUKE2 37 +#define MZ_NUKE4 38 +#define MZ_NUKE8 39 +//ROGUE + +#define MZ_SILENCED 128 // bit flag ORed with one of the above numbers + +// +// monster muzzle flashes +// +#define MZ2_TANK_BLASTER_1 1 +#define MZ2_TANK_BLASTER_2 2 +#define MZ2_TANK_BLASTER_3 3 +#define MZ2_TANK_MACHINEGUN_1 4 +#define MZ2_TANK_MACHINEGUN_2 5 +#define MZ2_TANK_MACHINEGUN_3 6 +#define MZ2_TANK_MACHINEGUN_4 7 +#define MZ2_TANK_MACHINEGUN_5 8 +#define MZ2_TANK_MACHINEGUN_6 9 +#define MZ2_TANK_MACHINEGUN_7 10 +#define MZ2_TANK_MACHINEGUN_8 11 +#define MZ2_TANK_MACHINEGUN_9 12 +#define MZ2_TANK_MACHINEGUN_10 13 +#define MZ2_TANK_MACHINEGUN_11 14 +#define MZ2_TANK_MACHINEGUN_12 15 +#define MZ2_TANK_MACHINEGUN_13 16 +#define MZ2_TANK_MACHINEGUN_14 17 +#define MZ2_TANK_MACHINEGUN_15 18 +#define MZ2_TANK_MACHINEGUN_16 19 +#define MZ2_TANK_MACHINEGUN_17 20 +#define MZ2_TANK_MACHINEGUN_18 21 +#define MZ2_TANK_MACHINEGUN_19 22 +#define MZ2_TANK_ROCKET_1 23 +#define MZ2_TANK_ROCKET_2 24 +#define MZ2_TANK_ROCKET_3 25 + +#define MZ2_INFANTRY_MACHINEGUN_1 26 +#define MZ2_INFANTRY_MACHINEGUN_2 27 +#define MZ2_INFANTRY_MACHINEGUN_3 28 +#define MZ2_INFANTRY_MACHINEGUN_4 29 +#define MZ2_INFANTRY_MACHINEGUN_5 30 +#define MZ2_INFANTRY_MACHINEGUN_6 31 +#define MZ2_INFANTRY_MACHINEGUN_7 32 +#define MZ2_INFANTRY_MACHINEGUN_8 33 +#define MZ2_INFANTRY_MACHINEGUN_9 34 +#define MZ2_INFANTRY_MACHINEGUN_10 35 +#define MZ2_INFANTRY_MACHINEGUN_11 36 +#define MZ2_INFANTRY_MACHINEGUN_12 37 +#define MZ2_INFANTRY_MACHINEGUN_13 38 + +#define MZ2_SOLDIER_BLASTER_1 39 +#define MZ2_SOLDIER_BLASTER_2 40 +#define MZ2_SOLDIER_SHOTGUN_1 41 +#define MZ2_SOLDIER_SHOTGUN_2 42 +#define MZ2_SOLDIER_MACHINEGUN_1 43 +#define MZ2_SOLDIER_MACHINEGUN_2 44 + +#define MZ2_GUNNER_MACHINEGUN_1 45 +#define MZ2_GUNNER_MACHINEGUN_2 46 +#define MZ2_GUNNER_MACHINEGUN_3 47 +#define MZ2_GUNNER_MACHINEGUN_4 48 +#define MZ2_GUNNER_MACHINEGUN_5 49 +#define MZ2_GUNNER_MACHINEGUN_6 50 +#define MZ2_GUNNER_MACHINEGUN_7 51 +#define MZ2_GUNNER_MACHINEGUN_8 52 +#define MZ2_GUNNER_GRENADE_1 53 +#define MZ2_GUNNER_GRENADE_2 54 +#define MZ2_GUNNER_GRENADE_3 55 +#define MZ2_GUNNER_GRENADE_4 56 + +#define MZ2_CHICK_ROCKET_1 57 + +#define MZ2_FLYER_BLASTER_1 58 +#define MZ2_FLYER_BLASTER_2 59 + +#define MZ2_MEDIC_BLASTER_1 60 + +#define MZ2_GLADIATOR_RAILGUN_1 61 + +#define MZ2_HOVER_BLASTER_1 62 + +#define MZ2_ACTOR_MACHINEGUN_1 63 + +#define MZ2_SUPERTANK_MACHINEGUN_1 64 +#define MZ2_SUPERTANK_MACHINEGUN_2 65 +#define MZ2_SUPERTANK_MACHINEGUN_3 66 +#define MZ2_SUPERTANK_MACHINEGUN_4 67 +#define MZ2_SUPERTANK_MACHINEGUN_5 68 +#define MZ2_SUPERTANK_MACHINEGUN_6 69 +#define MZ2_SUPERTANK_ROCKET_1 70 +#define MZ2_SUPERTANK_ROCKET_2 71 +#define MZ2_SUPERTANK_ROCKET_3 72 + +#define MZ2_BOSS2_MACHINEGUN_L1 73 +#define MZ2_BOSS2_MACHINEGUN_L2 74 +#define MZ2_BOSS2_MACHINEGUN_L3 75 +#define MZ2_BOSS2_MACHINEGUN_L4 76 +#define MZ2_BOSS2_MACHINEGUN_L5 77 +#define MZ2_BOSS2_ROCKET_1 78 +#define MZ2_BOSS2_ROCKET_2 79 +#define MZ2_BOSS2_ROCKET_3 80 +#define MZ2_BOSS2_ROCKET_4 81 + +#define MZ2_FLOAT_BLASTER_1 82 + +#define MZ2_SOLDIER_BLASTER_3 83 +#define MZ2_SOLDIER_SHOTGUN_3 84 +#define MZ2_SOLDIER_MACHINEGUN_3 85 +#define MZ2_SOLDIER_BLASTER_4 86 +#define MZ2_SOLDIER_SHOTGUN_4 87 +#define MZ2_SOLDIER_MACHINEGUN_4 88 +#define MZ2_SOLDIER_BLASTER_5 89 +#define MZ2_SOLDIER_SHOTGUN_5 90 +#define MZ2_SOLDIER_MACHINEGUN_5 91 +#define MZ2_SOLDIER_BLASTER_6 92 +#define MZ2_SOLDIER_SHOTGUN_6 93 +#define MZ2_SOLDIER_MACHINEGUN_6 94 +#define MZ2_SOLDIER_BLASTER_7 95 +#define MZ2_SOLDIER_SHOTGUN_7 96 +#define MZ2_SOLDIER_MACHINEGUN_7 97 +#define MZ2_SOLDIER_BLASTER_8 98 +#define MZ2_SOLDIER_SHOTGUN_8 99 +#define MZ2_SOLDIER_MACHINEGUN_8 100 + +// --- Xian shit below --- +#define MZ2_MAKRON_BFG 101 +#define MZ2_MAKRON_BLASTER_1 102 +#define MZ2_MAKRON_BLASTER_2 103 +#define MZ2_MAKRON_BLASTER_3 104 +#define MZ2_MAKRON_BLASTER_4 105 +#define MZ2_MAKRON_BLASTER_5 106 +#define MZ2_MAKRON_BLASTER_6 107 +#define MZ2_MAKRON_BLASTER_7 108 +#define MZ2_MAKRON_BLASTER_8 109 +#define MZ2_MAKRON_BLASTER_9 110 +#define MZ2_MAKRON_BLASTER_10 111 +#define MZ2_MAKRON_BLASTER_11 112 +#define MZ2_MAKRON_BLASTER_12 113 +#define MZ2_MAKRON_BLASTER_13 114 +#define MZ2_MAKRON_BLASTER_14 115 +#define MZ2_MAKRON_BLASTER_15 116 +#define MZ2_MAKRON_BLASTER_16 117 +#define MZ2_MAKRON_BLASTER_17 118 +#define MZ2_MAKRON_RAILGUN_1 119 +#define MZ2_JORG_MACHINEGUN_L1 120 +#define MZ2_JORG_MACHINEGUN_L2 121 +#define MZ2_JORG_MACHINEGUN_L3 122 +#define MZ2_JORG_MACHINEGUN_L4 123 +#define MZ2_JORG_MACHINEGUN_L5 124 +#define MZ2_JORG_MACHINEGUN_L6 125 +#define MZ2_JORG_MACHINEGUN_R1 126 +#define MZ2_JORG_MACHINEGUN_R2 127 +#define MZ2_JORG_MACHINEGUN_R3 128 +#define MZ2_JORG_MACHINEGUN_R4 129 +#define MZ2_JORG_MACHINEGUN_R5 130 +#define MZ2_JORG_MACHINEGUN_R6 131 +#define MZ2_JORG_BFG_1 132 +#define MZ2_BOSS2_MACHINEGUN_R1 133 +#define MZ2_BOSS2_MACHINEGUN_R2 134 +#define MZ2_BOSS2_MACHINEGUN_R3 135 +#define MZ2_BOSS2_MACHINEGUN_R4 136 +#define MZ2_BOSS2_MACHINEGUN_R5 137 + +//ROGUE +#define MZ2_CARRIER_MACHINEGUN_L1 138 +#define MZ2_CARRIER_MACHINEGUN_R1 139 +#define MZ2_CARRIER_GRENADE 140 +#define MZ2_TURRET_MACHINEGUN 141 +#define MZ2_TURRET_ROCKET 142 +#define MZ2_TURRET_BLASTER 143 +#define MZ2_STALKER_BLASTER 144 +#define MZ2_DAEDALUS_BLASTER 145 +#define MZ2_MEDIC_BLASTER_2 146 +#define MZ2_CARRIER_RAILGUN 147 +#define MZ2_WIDOW_DISRUPTOR 148 +#define MZ2_WIDOW_BLASTER 149 +#define MZ2_WIDOW_RAIL 150 +#define MZ2_WIDOW_PLASMABEAM 151 // PMM - not used +#define MZ2_CARRIER_MACHINEGUN_L2 152 +#define MZ2_CARRIER_MACHINEGUN_R2 153 +#define MZ2_WIDOW_RAIL_LEFT 154 +#define MZ2_WIDOW_RAIL_RIGHT 155 +#define MZ2_WIDOW_BLASTER_SWEEP1 156 +#define MZ2_WIDOW_BLASTER_SWEEP2 157 +#define MZ2_WIDOW_BLASTER_SWEEP3 158 +#define MZ2_WIDOW_BLASTER_SWEEP4 159 +#define MZ2_WIDOW_BLASTER_SWEEP5 160 +#define MZ2_WIDOW_BLASTER_SWEEP6 161 +#define MZ2_WIDOW_BLASTER_SWEEP7 162 +#define MZ2_WIDOW_BLASTER_SWEEP8 163 +#define MZ2_WIDOW_BLASTER_SWEEP9 164 +#define MZ2_WIDOW_BLASTER_100 165 +#define MZ2_WIDOW_BLASTER_90 166 +#define MZ2_WIDOW_BLASTER_80 167 +#define MZ2_WIDOW_BLASTER_70 168 +#define MZ2_WIDOW_BLASTER_60 169 +#define MZ2_WIDOW_BLASTER_50 170 +#define MZ2_WIDOW_BLASTER_40 171 +#define MZ2_WIDOW_BLASTER_30 172 +#define MZ2_WIDOW_BLASTER_20 173 +#define MZ2_WIDOW_BLASTER_10 174 +#define MZ2_WIDOW_BLASTER_0 175 +#define MZ2_WIDOW_BLASTER_10L 176 +#define MZ2_WIDOW_BLASTER_20L 177 +#define MZ2_WIDOW_BLASTER_30L 178 +#define MZ2_WIDOW_BLASTER_40L 179 +#define MZ2_WIDOW_BLASTER_50L 180 +#define MZ2_WIDOW_BLASTER_60L 181 +#define MZ2_WIDOW_BLASTER_70L 182 +#define MZ2_WIDOW_RUN_1 183 +#define MZ2_WIDOW_RUN_2 184 +#define MZ2_WIDOW_RUN_3 185 +#define MZ2_WIDOW_RUN_4 186 +#define MZ2_WIDOW_RUN_5 187 +#define MZ2_WIDOW_RUN_6 188 +#define MZ2_WIDOW_RUN_7 189 +#define MZ2_WIDOW_RUN_8 190 +#define MZ2_CARRIER_ROCKET_1 191 +#define MZ2_CARRIER_ROCKET_2 192 +#define MZ2_CARRIER_ROCKET_3 193 +#define MZ2_CARRIER_ROCKET_4 194 +#define MZ2_WIDOW2_BEAMER_1 195 +#define MZ2_WIDOW2_BEAMER_2 196 +#define MZ2_WIDOW2_BEAMER_3 197 +#define MZ2_WIDOW2_BEAMER_4 198 +#define MZ2_WIDOW2_BEAMER_5 199 +#define MZ2_WIDOW2_BEAM_SWEEP_1 200 +#define MZ2_WIDOW2_BEAM_SWEEP_2 201 +#define MZ2_WIDOW2_BEAM_SWEEP_3 202 +#define MZ2_WIDOW2_BEAM_SWEEP_4 203 +#define MZ2_WIDOW2_BEAM_SWEEP_5 204 +#define MZ2_WIDOW2_BEAM_SWEEP_6 205 +#define MZ2_WIDOW2_BEAM_SWEEP_7 206 +#define MZ2_WIDOW2_BEAM_SWEEP_8 207 +#define MZ2_WIDOW2_BEAM_SWEEP_9 208 +#define MZ2_WIDOW2_BEAM_SWEEP_10 209 +#define MZ2_WIDOW2_BEAM_SWEEP_11 210 + +// ROGUE + +extern vec3_t monster_flash_offset []; + + +// temp entity events +// +// Temp entity events are for things that happen +// at a location seperate from any existing entity. +// Temporary entity messages are explicitly constructed +// and broadcast. +typedef enum +{ + TE_GUNSHOT, + TE_BLOOD, + TE_BLASTER, + TE_RAILTRAIL, + TE_SHOTGUN, + TE_EXPLOSION1, + TE_EXPLOSION2, + TE_ROCKET_EXPLOSION, + TE_GRENADE_EXPLOSION, + TE_SPARKS, + TE_SPLASH, + TE_BUBBLETRAIL, + TE_SCREEN_SPARKS, + TE_SHIELD_SPARKS, + TE_BULLET_SPARKS, + TE_LASER_SPARKS, + TE_PARASITE_ATTACK, + TE_ROCKET_EXPLOSION_WATER, + TE_GRENADE_EXPLOSION_WATER, + TE_MEDIC_CABLE_ATTACK, + TE_BFG_EXPLOSION, + TE_BFG_BIGEXPLOSION, + TE_BOSSTPORT, // used as '22' in a map, so DON'T RENUMBER!!! + TE_BFG_LASER, + TE_GRAPPLE_CABLE, + TE_WELDING_SPARKS, + TE_GREENBLOOD, + TE_BLUEHYPERBLASTER, + TE_PLASMA_EXPLOSION, + TE_TUNNEL_SPARKS, +//ROGUE + TE_BLASTER2, + TE_RAILTRAIL2, + TE_FLAME, + TE_LIGHTNING, + TE_DEBUGTRAIL, + TE_PLAIN_EXPLOSION, + TE_FLASHLIGHT, + TE_FORCEWALL, + TE_HEATBEAM, + TE_MONSTER_HEATBEAM, + TE_STEAM, + TE_BUBBLETRAIL2, + TE_MOREBLOOD, + TE_HEATBEAM_SPARKS, + TE_HEATBEAM_STEAM, + TE_CHAINFIST_SMOKE, + TE_ELECTRIC_SPARKS, + TE_TRACKER_EXPLOSION, + TE_TELEPORT_EFFECT, + TE_DBALL_GOAL, + TE_WIDOWBEAMOUT, + TE_NUKEBLAST, + TE_WIDOWSPLASH, + TE_EXPLOSION1_BIG, + TE_EXPLOSION1_NP, + TE_FLECHETTE +//ROGUE +} temp_event_t; + +#define SPLASH_UNKNOWN 0 +#define SPLASH_SPARKS 1 +#define SPLASH_BLUE_WATER 2 +#define SPLASH_BROWN_WATER 3 +#define SPLASH_SLIME 4 +#define SPLASH_LAVA 5 +#define SPLASH_BLOOD 6 + + +// sound channels +// channel 0 never willingly overrides +// other channels (1-7) allways override a playing sound on that channel +#define CHAN_AUTO 0 +#define CHAN_WEAPON 1 +#define CHAN_VOICE 2 +#define CHAN_ITEM 3 +#define CHAN_BODY 4 +// modifier flags +#define CHAN_NO_PHS_ADD 8 // send to all clients, not just ones in PHS (ATTN 0 will also do this) +#define CHAN_RELIABLE 16 // send by reliable message, not datagram + + +// sound attenuation values +#define ATTN_NONE 0 // full volume the entire level +#define ATTN_NORM 1 +#define ATTN_IDLE 2 +#define ATTN_STATIC 3 // diminish very rapidly with distance + + +// player_state->stats[] indexes +#define STAT_HEALTH_ICON 0 +#define STAT_HEALTH 1 +#define STAT_AMMO_ICON 2 +#define STAT_AMMO 3 +#define STAT_ARMOR_ICON 4 +#define STAT_ARMOR 5 +#define STAT_SELECTED_ICON 6 +#define STAT_PICKUP_ICON 7 +#define STAT_PICKUP_STRING 8 +#define STAT_TIMER_ICON 9 +#define STAT_TIMER 10 +#define STAT_HELPICON 11 +#define STAT_SELECTED_ITEM 12 +#define STAT_LAYOUTS 13 +#define STAT_FRAGS 14 +#define STAT_FLASHES 15 // cleared each frame, 1 = health, 2 = armor +#define STAT_CHASE 16 +#define STAT_SPECTATOR 17 + +#define STAT_SIGHT_PIC 31 + +#define MAX_STATS 32 + + +// dmflags->value flags +#define DF_NO_HEALTH 0x00000001 // 1 +#define DF_NO_ITEMS 0x00000002 // 2 +#define DF_WEAPONS_STAY 0x00000004 // 4 +#define DF_NO_FALLING 0x00000008 // 8 +#define DF_INSTANT_ITEMS 0x00000010 // 16 +#define DF_SAME_LEVEL 0x00000020 // 32 +#define DF_SKINTEAMS 0x00000040 // 64 +#define DF_MODELTEAMS 0x00000080 // 128 +#define DF_NO_FRIENDLY_FIRE 0x00000100 // 256 +#define DF_SPAWN_FARTHEST 0x00000200 // 512 +#define DF_FORCE_RESPAWN 0x00000400 // 1024 +#define DF_NO_ARMOR 0x00000800 // 2048 +#define DF_ALLOW_EXIT 0x00001000 // 4096 +#define DF_INFINITE_AMMO 0x00002000 // 8192 +#define DF_QUAD_DROP 0x00004000 // 16384 +#define DF_FIXED_FOV 0x00008000 // 32768 + +// RAFAEL +#define DF_QUADFIRE_DROP 0x00010000 // 65536 + +//ROGUE +#define DF_NO_MINES 0x00020000 +#define DF_NO_STACK_DOUBLE 0x00040000 +#define DF_NO_NUKES 0x00080000 +#define DF_NO_SPHERES 0x00100000 +//ROGUE +#define ROGUE_VERSION_ID 1278 + +#define ROGUE_VERSION_STRING "08/21/1998 Beta 2 for Ensemble" +/* +========================================================== + + ELEMENTS COMMUNICATED ACROSS THE NET + +========================================================== +*/ + +#define ANGLE2SHORT(x) ((int)((x)*65536/360) & 65535) +#define SHORT2ANGLE(x) ((x)*(360.0/65536)) + + +// +// config strings are a general means of communication from +// the server to all connected clients. +// Each config string can be at most MAX_QPATH characters. +// +#define CS_NAME 0 +#define CS_CDTRACK 1 +#define CS_SKY 2 +#define CS_SKYAXIS 3 // %f %f %f format +#define CS_SKYROTATE 4 +#define CS_STATUSBAR 5 // display program string + +#define CS_AIRACCEL 29 // air acceleration control +#define CS_MAXCLIENTS 30 +#define CS_MAPCHECKSUM 31 // for catching cheater maps + +#define CS_MODELS 32 +#define CS_SOUNDS (CS_MODELS+MAX_MODELS) +#define CS_IMAGES (CS_SOUNDS+MAX_SOUNDS) +#define CS_LIGHTS (CS_IMAGES+MAX_IMAGES) +#define CS_ITEMS (CS_LIGHTS+MAX_LIGHTSTYLES) +#define CS_PLAYERSKINS (CS_ITEMS+MAX_ITEMS) +#define CS_GENERAL (CS_PLAYERSKINS+MAX_CLIENTS) +#define MAX_CONFIGSTRINGS (CS_GENERAL+MAX_GENERAL) + + +//============================================== + + +// entity_state_t->event values +// ertity events are for effects that take place reletive +// to an existing entities origin. Very network efficient. +// All muzzle flashes really should be converted to events... +typedef enum +{ + EV_NONE, + EV_ITEM_RESPAWN, + EV_FOOTSTEP, + EV_FALLSHORT, + EV_FALL, + EV_FALLFAR, + EV_PLAYER_TELEPORT, + EV_OTHER_TELEPORT +} entity_event_t; + + +// entity_state_t is the information conveyed from the server +// in an update message about entities that the client will +// need to render in some way +typedef struct entity_state_s +{ + int number; // edict index + + vec3_t origin; + vec3_t angles; + vec3_t old_origin; // for lerping + int modelindex; + int modelindex2, modelindex3, modelindex4; // weapons, CTF flags, etc + int frame; + int skinnum; + unsigned int effects; // PGM - we're filling it, so it needs to be unsigned + int renderfx; + int solid; // for client side prediction, 8*(bits 0-4) is x/y radius + // 8*(bits 5-9) is z down distance, 8(bits10-15) is z up + // gi.linkentity sets this properly + int sound; // for looping sounds, to guarantee shutoff + int event; // impulse events -- muzzle flashes, footsteps, etc + // events only go out for a single frame, they + // are automatically cleared each frame +} entity_state_t; + +//============================================== + + +// player_state_t is the information needed in addition to pmove_state_t +// to rendered a view. There will only be 10 player_state_t sent each second, +// but the number of pmove_state_t changes will be reletive to client +// frame rates +typedef struct +{ + pmove_state_t pmove; // for prediction + + // these fields do not need to be communicated bit-precise + + vec3_t viewangles; // for fixed views + vec3_t viewoffset; // add to pmovestate->origin + vec3_t kick_angles; // add to view direction to get render angles + // set by weapon kicks, pain effects, etc + + vec3_t gunangles; + vec3_t gunoffset; + int gunindex; + int gunframe; + + float blend[4]; // rgba full screen effect + + float fov; // horizontal field of view + + int rdflags; // refdef flags + + short stats[MAX_STATS]; // fast status bar updates +} player_state_t; + +// ================== +// PGM +#define VIDREF_GL 1 +#define VIDREF_SOFT 2 +#define VIDREF_OTHER 3 + +extern int vidref_val; +// PGM +// ================== + +#endif