Capture the Flag, 2008 Jun 17
[SauerbratenRemote.git] / src / rpggame / rpgent.h
blobff3fb31a6f3983b412dd838c19d51308239b5736
1 struct rpgent : dynent
3 rpgobj *ro;
4 rpgclient &cl;
6 int lastaction, lastpain;
7 bool attacking;
9 bool magicprojectile;
10 vec mppos, mpdir;
11 rpgobj *mpweapon;
12 float mpdist;
14 rpgent *enemy;
16 enum { R_STARE, R_ROAM, R_SEEK, R_ATTACK, R_BLOCKED, R_BACKHOME };
17 int npcstate;
19 int trigger;
21 float sink;
23 vec home;
25 enum { ROTSPEED = 200 };
27 rpgent(rpgobj *_ro, rpgclient &_cl, const vec &_pos, float _yaw, int _maxspeed = 40, int _type = ENT_AI) : ro(_ro), cl(_cl), lastaction(0), lastpain(0), attacking(false), magicprojectile(false), enemy(NULL), npcstate(R_STARE), trigger(0), sink(0)
29 o = _pos;
30 home = _pos;
31 yaw = _yaw;
32 maxspeed = _maxspeed;
33 type = _type;
34 enemy = &cl.player1;
37 float vecyaw(vec &t) { return -(float)atan2(t.x-o.x, t.y-o.y)/RAD+180; }
39 static const int ATTACKSAMPLES = 32;
41 void tryattackobj(rpgobj &eo, rpgobj &weapon)
43 if(!eo.s_ai || (eo.s_ai==ro->s_ai && eo.ent!=enemy)) return;
45 rpgent &e = *eo.ent;
46 if(e.state!=CS_ALIVE) return;
48 vec d = e.o;
49 d.sub(o);
50 d.z = 0;
51 if(d.magnitude()>e.radius+weapon.s_maxrange) return;
53 if(o.z+aboveeye<=e.o.z-e.eyeheight || o.z-eyeheight>=e.o.z+e.aboveeye) return;
55 vec p(0, 0, 0), closep;
56 float closedist = 1e10f;
57 loopj(ATTACKSAMPLES)
59 p.x = e.xradius * cosf(2*M_PI*j/ATTACKSAMPLES);
60 p.y = e.yradius * sinf(2*M_PI*j/ATTACKSAMPLES);
61 p.rotate_around_z((e.yaw+90)*RAD);
63 p.x += e.o.x;
64 p.y += e.o.y;
65 float tyaw = vecyaw(p);
66 normalize_yaw(tyaw);
67 if(fabs(tyaw-yaw)>weapon.s_maxangle) continue;
69 float dx = p.x-o.x, dy = p.y-o.y, dist = dx*dx + dy*dy;
70 if(dist<closedist) { closedist = dist; closep = p; }
73 if(closedist>weapon.s_maxrange*weapon.s_maxrange) return;
75 weapon.useaction(eo, *this, true);
78 #define loopallrpgobjsexcept(ro) loop(i, cl.os.set.length()+1) for(rpgobj *eo = i ? cl.os.set[i-1] : cl.os.playerobj; eo; eo = NULL) if((ro)!=eo)
80 void tryattack(vec &lookatpos, rpgobj &weapon)
82 if(cl.lastmillis-lastaction<weapon.s_attackrate) return;
84 lastaction = cl.lastmillis;
86 switch(weapon.s_usetype)
88 case 1:
89 if(!weapon.s_damage) return;
90 weapon.usesound(this);
91 loopallrpgobjsexcept(ro) tryattackobj(*eo, weapon);
92 break;
94 case 2:
96 if(!weapon.s_damage) return;
97 weapon.usesound(this);
98 particle_splash(0, 200, 250, lookatpos);
99 vec flarestart = o;
100 flarestart.z -= 2;
101 particle_flare(flarestart, lookatpos, 600, 10); // FIXME hudgunorigin(), and shorten to maxrange
102 float bestdist = 1e16f;
103 rpgobj *best = NULL;
104 loopallrpgobjsexcept(ro)
106 if(eo->ent->state!=CS_ALIVE) continue;
107 if(!intersect(eo->ent, o, lookatpos)) continue;
108 float dist = o.dist(eo->ent->o);
109 if(dist<weapon.s_maxrange && dist<bestdist)
111 best = eo;
112 bestdist = dist;
115 if(best) weapon.useaction(*best, *this, true);
116 break;
118 case 3:
119 if(weapon.s_maxrange) // projectile, cast on target
121 if(magicprojectile) return; // only one in the air at once
122 if(!ro->usemana(weapon)) return;
124 magicprojectile = true;
125 mpweapon = &weapon;
126 mppos = o;
127 //mpdir = vec(yaw*RAD, pitch*RAD);
128 float worlddist = lookatpos.dist(o, mpdir);
129 mpdir.normalize();
130 mpdist = min(float(weapon.s_maxrange), worlddist);
132 else
134 weapon.useaction(*ro, *this, true); // cast on self
136 break;
141 void updateprojectile(int curtime)
143 if(!magicprojectile) return;
145 regular_particle_splash(1, 2, 300, mppos);
146 particle_splash(mpweapon->s_effect, 1, 1, mppos);
148 float dist = curtime/5.0f;
149 if((mpdist -= dist)<0) { magicprojectile = false; return; };
151 vec mpto = vec(mpdir).mul(dist).add(mppos);
153 loopallrpgobjsexcept(ro) // FIXME: make fast "give me all rpgobs in range R that are not X" function
155 if(eo->ent->o.dist(mppos)<32 && intersect(eo->ent, mppos, mpto)) // quick reject, for now
157 magicprojectile = false;
158 mpweapon->useaction(*eo, *this, false); // cast on target
162 mppos = mpto;
165 void transition(int _state, int _moving, int n)
167 npcstate = _state;
168 move = _moving;
169 trigger = cl.lastmillis+n;
172 void gotoyaw(float yaw, int s, int m, int t)
174 targetyaw = yaw;
175 rotspeed = ROTSPEED;
176 transition(s, m, t);
179 void gotopos(vec &pos, int s, int m, int t) { gotoyaw(vecyaw(pos), s, m, t); }
181 void goroam()
183 if(home.dist(o)>128 && npcstate!=R_BACKHOME) gotopos(home, R_ROAM, 1, 1000);
184 else gotoyaw(targetyaw+90+rnd(180), R_ROAM, 1, 1000);
187 void stareorroam()
189 if(rnd(10)) transition(R_STARE, 0, 500);
190 else goroam();
193 void update(int curtime, float playerdist)
195 updateprojectile(curtime);
197 if(state==CS_DEAD) { stopmoving(); return; };
199 if(blocked && npcstate!=R_BLOCKED && npcstate!=R_SEEK)
201 blocked = false;
202 gotoyaw(targetyaw+90+rnd(180), R_BLOCKED, 1, 1000);
205 if(ro->s_hp<ro->eff_maxhp() && npcstate!=R_SEEK) gotopos(enemy->o, R_SEEK, 1, 200);
207 #define ifnextstate if(trigger<cl.lastmillis)
208 #define ifplayerclose if(playerdist<64)
210 switch(npcstate)
212 case R_BLOCKED:
213 case R_BACKHOME:
214 ifnextstate goroam();
215 break;
217 case R_STARE:
218 ifplayerclose
220 if(ro->s_ai==2) { gotopos(cl.player1.o, R_SEEK, 1, 200); enemy = &cl.player1; }
221 else { gotopos(cl.player1.o, R_STARE, 0, 500); }
223 else ifnextstate stareorroam();
224 break;
226 case R_ROAM:
227 ifplayerclose transition(R_STARE, 0, 500);
228 else ifnextstate stareorroam();
229 break;
231 case R_SEEK:
232 ifnextstate
234 vec target;
235 if(raycubelos(o, enemy->o, target))
237 rpgobj &weapon = ro->selectedweapon();
238 if(target.dist(o)<weapon.s_maxrange) tryattack(target, weapon);
240 if(enemy->o.dist(o)>256) goroam();
241 else gotopos(enemy->o, R_SEEK, 1, 100);
245 #undef ifnextstate
246 #undef ifplayerclose
249 void updateplayer(int curtime, vec &lookatpos) // alternative version of update() if this ent is the main player
251 updateprojectile(curtime);
253 if(state==CS_DEAD)
255 //lastaction = lastmillis;
256 if(cl.lastmillis-lastaction>5000)
258 conoutf("\f2you were found unconscious, and have been carried back home");
260 findplayerspawn(this);
261 state = CS_ALIVE;
262 ro->st_respawn();
263 attacking = false;
264 particle_splash(2, 1000, 500, o);
267 else
269 moveplayer(this, 20, true);
270 ro->st_update(cl.lastmillis);
271 if(attacking) tryattack(lookatpos, ro->selectedweapon());