6 int lastaction
, lastpain
;
16 enum { R_STARE
, R_ROAM
, R_SEEK
, R_ATTACK
, R_BLOCKED
, R_BACKHOME
};
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)
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;
46 if(e
.state
!=CS_ALIVE
) return;
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
;
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
);
65 float tyaw
= vecyaw(p
);
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
)
89 if(!weapon
.s_damage
) return;
90 weapon
.usesound(this);
91 loopallrpgobjsexcept(ro
) tryattackobj(*eo
, weapon
);
96 if(!weapon
.s_damage
) return;
97 weapon
.usesound(this);
98 particle_splash(0, 200, 250, lookatpos
);
101 particle_flare(flarestart
, lookatpos
, 600, 10); // FIXME hudgunorigin(), and shorten to maxrange
102 float bestdist
= 1e16f
;
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
)
115 if(best
) weapon
.useaction(*best
, *this, true);
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;
127 //mpdir = vec(yaw*RAD, pitch*RAD);
128 float worlddist
= lookatpos
.dist(o
, mpdir
);
130 mpdist
= min(float(weapon
.s_maxrange
), worlddist
);
134 weapon
.useaction(*ro
, *this, true); // cast on self
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
165 void transition(int _state
, int _moving
, int n
)
169 trigger
= cl
.lastmillis
+n
;
172 void gotoyaw(float yaw
, int s
, int m
, int t
)
179 void gotopos(vec
&pos
, int s
, int m
, int t
) { gotoyaw(vecyaw(pos
), s
, m
, t
); }
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);
189 if(rnd(10)) transition(R_STARE
, 0, 500);
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
)
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)
214 ifnextstate
goroam();
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();
227 ifplayerclose
transition(R_STARE
, 0, 500);
228 else ifnextstate
stareorroam();
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);
249 void updateplayer(int curtime
, vec
&lookatpos
) // alternative version of update() if this ent is the main player
251 updateprojectile(curtime
);
255 //lastaction = lastmillis;
256 if(cl
.lastmillis
-lastaction
>5000)
258 conoutf("\f2you were found unconscious, and have been carried back home");
260 findplayerspawn(this);
264 particle_splash(2, 1000, 500, o
);
269 moveplayer(this, 20, true);
270 ro
->st_update(cl
.lastmillis
);
271 if(attacking
) tryattack(lookatpos
, ro
->selectedweapon());