scripted_sequences: They sorta work now, needs an engine with

ENGINE_ROUTING enabled however
This commit is contained in:
Marco Cawthorne 2020-03-27 10:37:01 +01:00
parent bb0b4d8f6c
commit e9326f4e1d
8 changed files with 540 additions and 51 deletions

View file

@ -14,11 +14,26 @@
* OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
typedef struct
{
vector dest;
int linkflags;
} nodeslist_t;
/* Begin calculating a route. The callback function will be called once the
* route has finished being calculated. The route must be memfreed once it is
* no longer needed. The route must be followed in reverse order (ie: the
* first node that must be reached is at index numnodes-1).
* If no route is available then the callback will be called with no nodes. */
void(entity, vector, int, void(entity, vector, int, nodeslist_t *)) route_calculate = #0:route_calculate;
enum {
MONSTER_IDLE,
MONSTER_WALK,
MONSTER_RUN,
MONSTER_DEAD
MONSTER_DEAD,
MONSTER_INSEQUENCE
};
enumflags {
@ -44,6 +59,15 @@ class CBaseMonster:CBaseEntity
vector base_mins;
vector base_maxs;
int base_health;
float m_flSequenceSpeed;
/* pathfinding */
int m_iNodes;
int m_iCurNode;
nodeslist_t *m_pRoute;
/* sequences */
string m_strRouteEnded;
void() CBaseMonster;
@ -59,6 +83,11 @@ class CBaseMonster:CBaseEntity
virtual void(string) Sound;
virtual float(entity, float) SendEntity;
virtual void() ParentUpdate;
virtual void() ClearRoute;
virtual void() CheckRoute;
virtual void() WalkRoute;
virtual void(vector) NewRoute;
};
void CBaseMonster::Sound(string msg)
@ -117,6 +146,87 @@ void CBaseMonster::IdleNoise(void)
}
void CBaseMonster::ClearRoute(void)
{
if (m_iNodes) {
m_iNodes = 0;
memfree(m_pRoute);
}
}
void CBaseMonster::CheckRoute(void)
{
float flDist;
if (!m_iNodes) {
return;
}
flDist = floor( vlen( m_pRoute[m_iCurNode].dest - origin ) );
if ( flDist < 64 ) {
print(sprintf("CBaseMonster::CheckNode: %s reached node\n", this.netname));
m_iCurNode--;
velocity = [0,0,0]; /* clamp friction */
}
if (m_iCurNode < 0) {
print(sprintf("CBaseMonster::CheckNode: %s reached end\n", this.netname));
/* trigger when required */
if (m_strRouteEnded) {
for ( entity t = world; ( t = find( t, CBaseTrigger::m_strTargetName, m_strRouteEnded) ); ) {
CBaseTrigger trigger = (CBaseTrigger) t;
if (trigger.Trigger != __NULL__) {
trigger.Trigger();
}
}
}
ClearRoute();
}
/*if ( flDist == m_flLastDist ) {
m_flNodeGiveup += frametime;
} else {
m_flNodeGiveup = bound( 0, m_flNodeGiveup - frametime, 1.0 );
}
m_flLastDist = flDist;
if ( m_flNodeGiveup >= 1.0f ) {
print(sprintf("CBaseMonster::CheckNode: %s gave up route\n",
this.netname));
ClearRoute();
}*/
}
void CBaseMonster::WalkRoute(void)
{
if (m_iNodes) {
vector endangles;
endangles = vectoangles(m_pRoute[m_iCurNode].dest - origin);
input_angles[1] = endangles[1];
input_movevalues = [m_flSequenceSpeed, 0, 0];
}
}
void CBaseMonster::NewRoute(vector destination)
{
/* engine calls this upon successfully creating a route */
static void NewRoute_RouteCB(entity ent, vector dest, int numnodes, nodeslist_t *nodelist)
{
CBaseMonster p = (CBaseMonster)ent;
p.m_iNodes = numnodes;
p.m_iCurNode = numnodes - 1;
p.m_pRoute = nodelist;
}
ClearRoute();
if (!m_iNodes) {
route_calculate(this, destination, 0, NewRoute_RouteCB);
}
}
void CBaseMonster::Physics(void)
{
input_movevalues = [0,0,0];
@ -127,10 +237,19 @@ void CBaseMonster::Physics(void)
input_timelength = frametime;
movetype = MOVETYPE_WALK;
CheckRoute();
WalkRoute();
runstandardplayerphysics(this);
movetype = MOVETYPE_NONE;
IdleNoise();
/* support for think/nextthink */
if (think && nextthink > 0) {
if (nextthink < time) {
think();
nextthink = 0.0f;
}
}
}
void CBaseMonster::touch(void)

View file

@ -472,6 +472,8 @@ CBaseNPC::Physics(void)
}
input_angles = angles = v_angle;
CheckRoute();
WalkRoute();
input_timelength = frametime;
runstandardplayerphysics(this);

View file

@ -22,18 +22,6 @@
Decorative, does nothing yet.
*/
enumflags
{
MF_WAITTILLSEEN,
MF_GAG,
MF_MONSTERCLIP,
MF_PRISONER,
MF_UNUSED,
MF_WAITFORSCRIPT,
MF_PREDISASTER,
MF_FADECORPSE
};
class monster_generic:CBaseEntity
{
void() monster_generic;

View file

@ -85,7 +85,7 @@ class scripted_sequence:CBaseTrigger
float m_flSearchRadius;
/* How we move to perform m_iActionAnim */
int m_iMove;
void() scripted_sequence;
virtual void() Trigger;
virtual void() Respawn;
@ -93,7 +93,26 @@ class scripted_sequence:CBaseTrigger
void scripted_sequence::Trigger(void)
{
CBaseMonster f;
print(sprintf("^2scripted_sequence::Trigger^7: with spawnflags %d\n", spawnflags));
if (m_iMove == SS_WALK) {
f = (CBaseMonster)find(world, CBaseEntity::m_strTargetName, m_strMonster);
if (f) {
f.NewRoute(origin);
f.style = MONSTER_INSEQUENCE;
f.m_flSequenceSpeed = 64;
f.m_strRouteEnded = m_strTarget;
}
} else if (m_iMove == SS_RUN) {
f = (CBaseMonster)find(world, CBaseEntity::m_strTargetName, m_strMonster);
if (f) {
f.NewRoute(origin);
f.style = MONSTER_INSEQUENCE;
f.m_flSequenceSpeed = 256;
f.m_strRouteEnded = m_strTarget;
}
}
}
void scripted_sequence::Respawn(void)
@ -106,6 +125,9 @@ void scripted_sequence::scripted_sequence(void)
int nfields = tokenize(__fullspawndata);
for (int i = 1; i < (nfields-1); i += 2) {
switch (argv(i)) {
case "target":
m_strTarget = argv(i+1);
break;
case "m_iszEntity":
m_strMonster = argv(i+1);
break;

View file

@ -15,6 +15,7 @@
*/
/* parse info_node entities and convert them to FTE compatible routing data */
#define NODE_DEBUG 1
typedef struct node_s {
vector origin;
@ -76,6 +77,45 @@ Nodes_Save(string filename)
fclose(wayfile);
}
void
Nodes_Load(string filename)
{
float wayfile = fopen(filename, FILE_READ);
if (wayfile < 0) {
return;
}
/* wipe whatever we've got */
for (int i = 0; i < g_iNodes; i++) {
memfree(g_pNodes[i].nb);
}
memfree(g_pNodes);
g_iNodes = 0;
tokenize(fgets(wayfile));
g_iNodes = stoi(argv(0));
g_pNodes = memalloc(sizeof(*g_pNodes) * g_iNodes);
for (int i = 0; i < g_iNodes; i++) {
tokenize(fgets(wayfile));
g_pNodes[i].origin[0] = stof(argv(0));
g_pNodes[i].origin[1] = stof(argv(1));
g_pNodes[i].origin[2] = stof(argv(2));
g_pNodes[i].radius = stof(argv(3));
g_pNodes[i].nb_count = stoi(argv(4));
g_pNodes[i].nb = memalloc(sizeof(*g_pNodes[i].nb) * g_pNodes[i].nb_count);
for (int j = 0; j < g_pNodes[i].nb_count; j++) {
tokenize(fgets(wayfile));
g_pNodes[i].nb[j].node = stoi(argv(0));
g_pNodes[i].nb[j].dist = stof(argv(1));
g_pNodes[i].nb[j].flags = stoh(argv(2));
}
}
fclose(wayfile);
}
/* link two nodes together */
static void
Node_Link(node_t *n1, node_t *n2)
@ -138,6 +178,9 @@ Nodes_Init(void)
/* skip if present. TODO: check if they're out of date? */
if (whichpack(sprintf("data/%s.way", mapname))) {
g_nodes_present = TRUE;
#ifdef NODE_DEBUG
Nodes_Load(sprintf("%s.way", mapname));
#endif
return;
}
@ -159,8 +202,8 @@ Nodes_Init(void)
Nodes_Save(sprintf("%s.way", mapname));
#ifndef GS_DEVELOPER
/* we don't need these any longer */
#ifndef NODE_DEBUG
for (int i = 0; i < g_iNodes; i++) {
memfree(g_pNodes[i].nb);
}
@ -169,11 +212,11 @@ Nodes_Init(void)
#endif
}
#ifdef NODE_DEBUG
/* draws debug graphics of our node tree */
void
SV_AddDebugPolygons(void)
{
#ifdef GS_DEVELOPER
if (!g_iNodes) {
return;
}
@ -232,5 +275,5 @@ SV_AddDebugPolygons(void)
R_EndPolygon();
}
}
#endif
}
#endif

View file

@ -20,18 +20,172 @@ Gonarch
*/
enum {
GON_IDLE,
GON_IDLE2,
GON_WALK,
GON_RUN,
GON_DIE,
GON_CLAW,
GON_CLAW2,
GON_CLAW3,
GON_SPAWN,
GON_SHOOT,
GON_FLINCH,
GON_DEFEND,
GON_JUMP,
GON_ANGRY,
GON_ANGRY2,
GON_ANGRY3,
GON_BREAKWALL,
GON_FALL,
GON_FALL2,
GON_FALLDIE
};
/* the attack sounds are for when she spits? */
string gon_sndattack[] = {
"gonarch/gon_attack1.wav",
"gonarch/gon_attack2.wav",
"gonarch/gon_attack3.wav"
};
/* mourns the death of her children */
string gon_sndchild[] = {
"gonarch/gon_childdie1.wav",
"gonarch/gon_childdie2.wav",
"gonarch/gon_childdie3.wav"
};
string gon_snddie[] = {
"gonarch/gon_die1.wav",
"gonarch/gon_die2.wav",
"gonarch/gon_die3.wav"
};
string gon_sndidle[] = {
"gonarch/gon_sack1.wav",
"gonarch/gon_sack2.wav",
"gonarch/gon_sack3.wav"
};
string gon_sndpain[] = {
"gonarch/gon_pain2.wav",
"gonarch/gon_pain3.wav",
"gonarch/gon_pain4.wav",
"gonarch/gon_pain5.wav"
};
string gon_sndsee[] = {
"gonarch/gon_alert1.wav",
"gonarch/gon_alert2.wav",
"gonarch/gon_alert3.wav"
};
/* has unique foot step sounds */
string gon_sndstep[] = {
"gonarch/gon_step1.wav",
"gonarch/gon_step2.wav",
"gonarch/gon_step3.wav"
};
class monster_bigmomma:CBaseMonster
{
float m_flIdleTime;
float m_flPainTime;
void() monster_bigmomma;
virtual void(int) Death;
virtual void(int) Pain;
virtual void(void) IdleNoise;
virtual void(void) Respawn;
};
void
monster_bigmomma::IdleNoise(void)
{
/* don't make noise if we're dead (corpse) */
if (style == MONSTER_DEAD) {
return;
}
if (m_flIdleTime > time) {
return;
}
/* timing needs to adjusted as sounds conflict */
m_flIdleTime = time + 2.0f + random(0,5);
int rand = floor(random(0, gon_sndidle.length));
Sound(gon_sndidle[rand]);
}
void
monster_bigmomma::Pain(int iHitBody)
{
CBaseMonster::Pain(iHitBody);
if (m_flPainTime > time) {
return;
}
if (random() < 0.25f) {
return;
}
int rand = floor(random(0,gon_sndpain.length));
Sound(gon_sndpain[rand]);
frame = GON_FLINCH;
m_flPainTime = time + 0.25f;
}
void
monster_bigmomma::Death(int iHitBody)
{
/* if we're already dead (corpse) don't change animations */
if (style != MONSTER_DEAD) {
frame = GON_DIE;
Sound("gonarch/gon_die1.wav");
}
/* set the functional differences */
CBaseMonster::Death(iHitBody);
}
void
monster_bigmomma::Respawn(void)
{
CBaseMonster::Respawn();
frame = GON_IDLE;
}
void monster_bigmomma::monster_bigmomma(void)
{
for (int i = 0; i <gon_sndattack.length; i++) {
precache_sound(gon_sndattack[i]);
}
for (int i = 0; i <gon_sndchild.length; i++) {
precache_sound(gon_sndchild[i]);
}
for (int i = 0; i < gon_sndidle.length; i++) {
precache_sound(gon_sndidle[i]);
}
for (int i = 0; i < gon_sndpain.length; i++) {
precache_sound(gon_sndpain[i]);
}
for (int i = 0; i <gon_sndsee.length; i++) {
precache_sound(gon_sndsee[i]);
}
for (int i = 0; i <gon_sndstep.length; i++) {
precache_sound(gon_sndstep[i]);
}
precache_sound("gonarch/gon_die1.wav");
netname = "Gonarch";
model = "models/big_mom.mdl";
/* health is based on factor, for it's not killable until last stage
* base_health = Skill_GetValue("bigmomma_health");
*/
/* health is based on factor, for it's not killable until last stage */
base_health = Skill_GetValue("bigmomma_health_factor") * 300;
base_mins = [-95,-95,0];
base_maxs = [95,95,190];
CBaseMonster::CBaseMonster();

View file

@ -21,23 +21,75 @@ Leech
*/
enum {
LE_SWIM,
LE_SWIM2,
LE_ATTACK,
LE_HOVER,
LE_LEFT,
LE_RIGHT,
LE_DIE,
LE_DIEEND
LEECH_SWIM,
LEECH_SWIM2,
LEECH_ATTACK,
LEECH_HOVER,
LEECH_LEFT,
LEECH_RIGHT,
LEECH_DIE,
LEECH_DIEEND
};
string leech_sndattack[] = {
"leech/leech_bite1.wav",
"leech/leech_bite2.wav",
"leech/leech_bite3.wav"
};
string leech_sndsee[] = {
"leech/leech_alert1.wav",
"leech/leech_alert2.wav"
};
class monster_leech:CBaseMonster
{
float m_flIdleTime;
float m_flPainTime;
void() monster_leech;
virtual void(int) Death;
virtual void() DeathEnd;
virtual void() Respawn;
};
void
monster_leech::DeathEnd(void)
{
frame = LEECH_DIEEND;
}
void
monster_leech::Death(int iHitBody)
{
/* if we're already dead (corpse) don't change animations */
if (style != MONSTER_DEAD) {
frame = LEECH_DIE;
think = DeathEnd;
nextthink = time + 1.0f;
}
/* set the functional differences */
CBaseMonster::Death(iHitBody);
}
void
monster_leech::Respawn(void)
{
CBaseMonster::Respawn();
frame = LEECH_SWIM;
}
void monster_leech::monster_leech(void)
{
for (int i = 0; i <leech_sndattack.length; i++) {
precache_sound(leech_sndattack[i]);
}
for (int i = 0; i < leech_sndsee.length; i++) {
precache_sound(leech_sndsee[i]);
}
netname = "Leech";
model = "models/leech.mdl";
base_mins = [-6,-6,0];

View file

@ -21,36 +21,145 @@ Nihilanth
*/
enum {
NIH_IDLE,
NIH_ATTACK,
NIH_ATTACK2,
NIH_THROW,
NIH_BLOCK,
NIH_RECHARGE,
NIH_IDLEOPEN,
NIH_ATTACKOPEN,
NIH_ATTACKOPEN2,
NIH_FLINCH,
NIH_FLINCH2,
NIH_FALL,
NIH_DIE,
NIH_FORWARD,
NIH_BACK,
NIH_UP,
NIH_DOWN,
NIH_RIGHT,
NIH_LEFT,
NIH_WALK2,
NIH_SHOOT
NIL_IDLE,
NIL_ATTACK,
NIL_ATTACK2,
NIL_THROW,
NIL_BLOCK,
NIL_RECHARGE,
NIL_IDLEOPEN,
NIL_ATTACKOPEN,
NIL_ATTACKOPEN2,
NIL_FLINCH,
NIL_FLINCH2,
NIL_FALL,
NIL_DIE,
NIL_FORWARD,
NIL_BACK,
NIL_UP,
NIL_DOWN,
NIL_RIGHT,
NIL_LEFT,
NIL_WALK2,
NIL_SHOOT
};
/* other sounds
* x_ballattack1 - the portal he casts
* x_shoot1 - ?
* x_teleattack1 - portal's move sound
* nih_die2 - used in map not code? */
/* these attack sounds are his growls */
string nil_sndattack[] = {
"x/x_attack1.wav",
"x/x_attack2.wav",
"x/x_attack3.wav"
};
string nil_sndidle[] = {
"x/x_laugh1.wav",
"x/x_laugh2.wav"
};
string nil_sndpain[] = {
"x/x_pain1.wav",
"x/x_pain2.wav",
"x/x_pain3.wav"
};
string nil_sndrecharge[] = {
"x/x_recharge1.wav",
"x/x_recharge2.wav",
"x/x_recharge3.wav"
};
class monster_nihilanth:CBaseMonster
{
float m_flIdleTime;
float m_flPainTime;
void() monster_nihilanth;
virtual void(int) Death;
virtual void(int) Pain;
virtual void(void) IdleNoise;
virtual void(void) Respawn;
};
void
monster_nihilanth::IdleNoise(void)
{
/* don't make noise if we're dead (corpse) */
if (style == MONSTER_DEAD) {
return;
}
if (m_flIdleTime > time) {
return;
}
/* timing needs to adjusted as sounds conflict */
m_flIdleTime = time + 2.0f + random(0,5);
int rand = floor(random(0, nil_sndidle.length));
Sound(nil_sndidle[rand]);
}
void
monster_nihilanth::Pain(int iHitBody)
{
CBaseMonster::Pain(iHitBody);
if (m_flPainTime > time) {
return;
}
if (random() < 0.25f) {
return;
}
int rand = floor(random(0,nil_sndpain.length));
Sound(nil_sndpain[rand]);
frame = (random() < 0.5) ? NIL_FLINCH : NIL_FLINCH2;
m_flPainTime = time + 0.25f;
}
void
monster_nihilanth::Death(int iHitBody)
{
/* if we're already dead (corpse) don't change animations */
if (style != MONSTER_DEAD) {
frame = NIL_DIE;
Sound("x/x_die1.wav");
}
/* set the functional differences */
CBaseMonster::Death(iHitBody);
}
void
monster_nihilanth::Respawn(void)
{
CBaseMonster::Respawn();
frame = NIL_IDLE;
}
void monster_nihilanth::monster_nihilanth(void)
{
for (int i = 0; i <nil_sndattack.length; i++) {
precache_sound(nil_sndattack[i]);
}
for (int i = 0; i < nil_sndidle.length; i++) {
precache_sound(nil_sndidle[i]);
}
for (int i = 0; i < nil_sndpain.length; i++) {
precache_sound(nil_sndpain[i]);
}
for (int i = 0; i < nil_sndrecharge.length; i++) {
precache_sound(nil_sndrecharge[i]);
}
netname = "Nihilanth";
model = "models/nihilanth.mdl";
base_health = Skill_GetValue("nihilanth_health");