]> git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/commitdiff
Add something truly monstrous (custom monsters)
authorMario <mario@smbclan.net>
Sat, 4 Mar 2017 00:14:04 +0000 (10:14 +1000)
committerMario <mario@smbclan.net>
Sat, 4 Mar 2017 00:14:04 +0000 (10:14 +1000)
qcsrc/common/monsters/monster/_mod.inc
qcsrc/common/monsters/monster/_mod.qh
qcsrc/common/monsters/monster/monster.qc [new file with mode: 0644]
qcsrc/common/monsters/monster/monster.qh [new file with mode: 0644]
qcsrc/common/monsters/spawner.qc
qcsrc/common/monsters/sv_monsters.qc
qcsrc/common/monsters/sv_monsters.qh

index 23e649ab875810fd2cee74738d10a18c2146d5ed..6bfcf943c8675c197118f83f7d9817d787716148 100644 (file)
@@ -1,5 +1,6 @@
 // generated file; do not modify
 #include <common/monsters/monster/mage.qc>
+#include <common/monsters/monster/monster.qc>
 #include <common/monsters/monster/shambler.qc>
 #include <common/monsters/monster/spider.qc>
 #include <common/monsters/monster/wyvern.qc>
index a1c048d0f4c9fdbb1bfd8ca1aabe86f9fd136904..5a3b913744d4a8c01779cad6d7249a1f549ea54a 100644 (file)
@@ -1,5 +1,6 @@
 // generated file; do not modify
 #include <common/monsters/monster/mage.qh>
+#include <common/monsters/monster/monster.qh>
 #include <common/monsters/monster/shambler.qh>
 #include <common/monsters/monster/spider.qh>
 #include <common/monsters/monster/wyvern.qh>
diff --git a/qcsrc/common/monsters/monster/monster.qc b/qcsrc/common/monsters/monster/monster.qc
new file mode 100644 (file)
index 0000000..5d63eda
--- /dev/null
@@ -0,0 +1,244 @@
+#include "monster.qh"
+
+#ifdef SVQC
+
+const int MON_MOVE_NONE = BIT(0);
+const int MON_MOVE_NORMAL = BIT(1);
+const int MON_MOVE_2D = BIT(2);
+const int MON_MOVE_BOUNCE = BIT(4);
+const int MON_MOVE_TOUCH = BIT(5);
+const int MON_MOVE_JUMP = BIT(6);
+
+const int MON_ATTACK_MELEE = BIT(0);
+const int MON_ATTACK_TOUCH = BIT(1);
+const int MON_ATTACK_PROJECTILE = BIT(2);
+
+const int MON_ATTACKTYPE_GRENADE = 0;
+const int MON_ATTACKTYPE_FIREBALL = 1;
+
+.int frame_walk, frame_run, frame_idle, frame_melee, frame_shoot, frame_death, frame_pain;
+
+// here come the fields
+.bool mon_jumpoff;
+.float mon_touchdelay, mon_touchangle;
+.float touch_timer; // reused field
+.float mon_jumpdelay, mon_jumpheight;
+.float jump_delay;
+.float shot_dmg, shot_radius;
+.float mon_proj_speed, mon_proj_speed_up;
+
+.int mon_movetype, mon_attacks, mon_attacktype;
+
+void M_CustomMonster_Touch(entity this, entity toucher)
+{
+       if((this.mon_movetype & MON_MOVE_TOUCH) && time < this.touch_timer && vdist(this.velocity, <, this.speed))
+       {
+               fixedmakevectors(toucher.angles);
+               this.velocity = v_forward * this.speed2;
+               this.touch_timer = time + this.mon_touchdelay;
+               return;
+       }
+
+       if(!(this.mon_attacks & MON_ATTACK_TOUCH))
+               return;
+       if(IS_DEAD(toucher))
+               return;
+       if(toucher.items & ITEM_Shield.m_itemid)
+               return;
+       if(toucher.takedamage == DAMAGE_NO)
+               return;
+       if(!toucher.iscreature)
+               return;
+       if(time < this.attack_finished_single[0])
+               return;
+       if(toucher.mdl == this.mdl || SAME_TEAM(this, toucher))
+               return; // friendly
+               
+       vector vdir = normalize(toucher.origin - this.origin);
+       if(vdir.z <= this.mon_touchangle)
+       {
+               Damage(toucher, this, this, this.dmg, DEATH_MONSTER_ZOMBIE_MELEE.m_id, toucher.origin, '0 0 0');
+               this.attack_finished_single[0] = time + this.delay;
+       }
+}
+
+void M_CustomMonster_Attack_Grenade_Touch(entity this, entity toucher)
+{
+       PROJECTILE_TOUCH(this, toucher);
+       // only 'explode' if we touch a player (or equally humanoid creature)
+       if((IS_PLAYER(toucher) || IS_MONSTER(toucher)) && this.velocity)
+               turret_projectile_explode(this);
+}
+
+void M_CustomMonster_TargetEnemey(entity this)
+{
+       if(time >= this.last_enemycheck)
+       {
+               if(!this.enemy)
+               {
+                       this.enemy = Monster_FindTarget(this);
+                       if(this.enemy)
+                               Monster_Sound(this, monstersound_sight, 0, false, CH_VOICE);
+               }
+
+               this.last_enemycheck = time + 1; // check for enemies every second
+       }
+
+       .entity weaponentity = weaponentities[0]; // TODO?
+       Monster_Attack_Check(this, this.enemy, weaponentity);
+}
+
+bool M_CustomMonster_Attack(int attack_type, entity actor, entity targ, .entity weaponentity)
+{
+       switch(attack_type)
+       {
+               case MONSTER_ATTACK_MELEE:
+               {
+                       if(actor.mon_attacks & MON_ATTACK_MELEE)
+                               return Monster_Attack_Melee(actor, actor.enemy, actor.dmg, actor.anim_melee, actor.attack_range, actor.delay, DEATH_MONSTER_ZOMBIE_MELEE.m_id, actor.wait); // just fall back to zombie melee deathtype
+                       return false;
+               }
+               case MONSTER_ATTACK_RANGED:
+               {
+                       if(actor.mon_attacks & MON_ATTACK_PROJECTILE)
+                       {
+                               int ptype = PROJECTILE_GRENADE;
+                               float psize = 1.6;
+                               if(actor.mon_attacktype == MON_ATTACKTYPE_FIREBALL)
+                               {
+                                       ptype = PROJECTILE_FIREMINE;
+                                       psize = 0.8;
+                               }
+                               entity proj = turret_projectile(actor, SND_Null, psize, 0, DEATH_MONSTER_ZOMBIE_JUMP.m_id, ptype, true, true);
+                               setorigin(proj, CENTER_OR_VIEWOFS(actor));
+                               if(actor.mon_attacktype == MON_ATTACKTYPE_GRENADE)
+                               {
+                                       proj.nextthink = time + 3;
+                                       proj.gravity = 1.0;
+                                       settouch(proj, M_CustomMonster_Attack_Grenade_Touch);
+                                       set_movetype(proj, MOVETYPE_TOSS);
+                                       proj.solid = SOLID_TRIGGER;
+                               }
+                               makevectors(actor.angles);
+                               W_SetupProjVelocity_Explicit(proj, v_forward, v_up, actor.mon_proj_speed, actor.mon_proj_speed_up, 0, 0, false);
+                               UpdateCSQCProjectile(proj);
+                               actor.attack_finished_single[0] = time + (actor.delay * random());
+                               return true;
+                       }
+                       return false;
+               }
+       }
+
+       return false;
+}
+
+spawnfunc(monster_custom) { Monster_Spawn(this, true, MON_MONSTER.monsterid); }
+#endif // SVQC
+
+#ifdef SVQC
+METHOD(CustomMonster, mr_think, bool(CustomMonster this, entity actor))
+{
+    TC(CustomMonster, this);
+    if((actor.mon_movetype & MON_MOVE_JUMP) && time < actor.jump_delay)
+    {
+       actor.velocity_z += actor.mon_jumpheight;
+       actor.jump_delay = time + (random() * actor.mon_jumpdelay);
+    }
+    if(actor.mon_movetype & MON_MOVE_2D)
+    {
+       Monster_Move_2D(actor, actor.speed, actor.mon_jumpoff);
+       M_CustomMonster_TargetEnemey(actor); // not called by regular code in this case
+       return false;
+    }
+    if(actor.mon_movetype & MON_MOVE_NORMAL)
+       return true;
+    // none handled automatically
+    M_CustomMonster_TargetEnemey(actor); // not called by regular code in this case
+    return false;
+}
+
+METHOD(CustomMonster, mr_pain, float(CustomMonster this, entity actor, float damage_take, entity attacker, float deathtype))
+{
+    TC(CustomMonster, this);
+    setanim(actor, actor.anim_pain1, true, true, false);
+    actor.pain_finished = actor.animstate_endtime;
+    return damage_take;
+}
+
+METHOD(CustomMonster, mr_death, bool(CustomMonster this, entity actor))
+{
+    TC(CustomMonster, this);
+    setanim(actor, actor.anim_melee, false, true, true);
+    return true;
+}
+#endif
+#ifdef GAMEQC
+METHOD(CustomMonster, mr_anim, bool(CustomMonster this, entity actor))
+{
+    TC(CustomMonster, this);
+    // TODO? Can this even be made to work with CSQC?
+#ifdef SVQC
+    vector none = '0 0 0';
+#define GF(fm,df) ((actor.(fm)) ? actor.(fm) - 1 : (df))
+    actor.anim_walk = animfixfps(actor, vec3(GF(frame_walk, 1), 1, 1), none);
+    actor.anim_idle = animfixfps(actor, vec3(GF(frame_idle, 0), 1, 1), none);
+    actor.anim_melee = animfixfps(actor, vec3(GF(frame_melee, 2), 1, 5), none); // analyze models and set framerate
+    actor.anim_shoot = animfixfps(actor, vec3(GF(frame_shoot, 3), 1, 5), none); // analyze models and set framerate
+    actor.anim_run = animfixfps(actor, vec3(GF(frame_run, 1), 1, 1), none);
+    actor.anim_die1 = animfixfps(actor, vec3(GF(frame_death, 2), 1, 1), none);
+    actor.anim_pain1 = animfixfps(actor, vec3(GF(frame_pain, 0), 1, 1), none);
+#undef GF
+#endif
+    return true;
+}
+#endif
+#ifdef SVQC
+METHOD(CustomMonster, mr_setup, bool(CustomMonster this, entity actor))
+{
+    TC(CustomMonster, this);
+    // hardcode some defaults so it isn't completely b0rked
+    if(!actor.health) actor.health = 100;
+    if(!actor.attack_range) actor.attack_range = 150;
+    //if(!actor.speed) { actor.speed = 200; }
+    //if(!actor.speed2) { actor.speed2 = 400; }
+    if(!actor.stopspeed) { actor.stopspeed = 100; }
+    if(!actor.damageforcescale) { actor.damageforcescale = 0.15; }
+    if(!actor.mon_touchdelay) { actor.mon_touchdelay = 0.2; }
+    if(!actor.mon_jumpdelay) { actor.mon_jumpdelay = 4; } // note: randomized
+    if(!actor.mon_jumpheight) { actor.mon_jumpheight = 300; }
+    if(!actor.mon_touchangle) { actor.mon_touchangle = 0.7; }
+    if(!actor.shot_dmg) { actor.shot_dmg = 50; }
+
+    // need to do this here, as the main code doesn't check the monster's spawnflags
+    if(actor.spawnflags & MONSTER_TYPE_SWIM)
+       actor.flags |= FL_SWIM;
+    if(actor.spawnflags & MONSTER_TYPE_FLY)
+       {
+               actor.flags |= FL_FLY;
+               set_movetype(actor, MOVETYPE_FLY);
+       }
+
+       if(actor.mon_movetype & MON_MOVE_BOUNCE)
+               set_movetype(actor, MOVETYPE_BOUNCE); // LOL
+
+    FOREACH(Items, it.netname == actor.debris,
+    {
+       actor.monster_loot = it;
+       break;
+    });
+
+    if(actor.mon_movetype == MON_MOVE_2D)
+       actor.ticrate = sys_frametime; // accuracy required
+
+    actor.monster_attackfunc = M_CustomMonster_Attack;
+    settouch(actor, M_CustomMonster_Touch);
+
+    return true;
+}
+
+METHOD(CustomMonster, mr_precache, bool(CustomMonster this))
+{
+    TC(CustomMonster, this);
+    return true;
+}
+#endif
diff --git a/qcsrc/common/monsters/monster/monster.qh b/qcsrc/common/monsters/monster/monster.qh
new file mode 100644 (file)
index 0000000..b403ede
--- /dev/null
@@ -0,0 +1,22 @@
+#pragma once
+
+#include "../all.qh"
+
+#ifdef GAMEQC
+MODEL(MON_MONSTER, M_Model("zombie.dpm"));
+#endif
+
+CLASS(CustomMonster, Monster)
+    ATTRIB(CustomMonster, spawnflags, int, MON_FLAG_HIDDEN);
+#ifdef GAMEQC
+    ATTRIB(CustomMonster, m_model, Model, MDL_MON_MONSTER);
+#endif
+    ATTRIB(CustomMonster, netname, string, "monster");
+    ATTRIB(CustomMonster, monster_name, string, _("Monster"));
+ENDCLASS(CustomMonster)
+
+REGISTER_MONSTER(MONSTER, NEW(CustomMonster)) {
+#ifdef GAMEQC
+    this.mr_precache(this);
+#endif
+}
index 0b34d13e659633a1892e10db93771a761ad4b629..a479c8a2e3afec219d6320d2997b5d48742f0566 100644 (file)
@@ -1,5 +1,7 @@
 #include "sv_spawn.qh"
 
+bool autocvar_g_monster_spawner_copyfields = false; // just incase this gets too nasty
+
 void spawner_use(entity this, entity actor, entity trigger)
 {
        int moncount = 0;
@@ -12,9 +14,21 @@ void spawner_use(entity this, entity actor, entity trigger)
                return;
 
        entity e = spawn();
-       e.noalign = this.noalign;
-       e.angles = this.angles;
-       e.monster_skill = this.monster_skill;
+       if(autocvar_g_monster_spawner_copyfields)
+       {
+               copyentity(this, e);
+               // we don't NEED to reset these, but might as well
+               e.classname = "monster";
+               e.use = func_null;
+               e.count = 0;
+       }
+       else
+       {
+               e.noalign = this.noalign;
+               e.angles = this.angles;
+               e.monster_skill = this.monster_skill;
+       }
+
        e = spawnmonster(e, this.spawnmob, 0, this, this, this.origin, false, true, this.monster_moveflags);
 }
 
index 5270fc2638714ff4dc418182b322720a31e9b8f4..80170e4725dae43c4ed4f50dc41f2c4c5baeceaa 100644 (file)
@@ -1012,8 +1012,16 @@ void Monster_Dead(entity this, entity attacker, float gibbed)
                totalspawned -= 1;
        }
 
+       Monster mon = Monsters_from(this.monsterid);
+
        if(!gibbed && this.mdl_dead && this.mdl_dead != "")
+       {
                _setmodel(this, this.mdl_dead);
+               if(this.oldmins && this.oldmaxs)
+                       setsize(this, this.oldmins * this.scale, this.oldmaxs * this.scale);
+               else
+                       setsize(this, mon.mins * this.scale, mon.maxs * this.scale);
+       }
 
        this.event_damage       = ((gibbed) ? func_null : Monster_Dead_Damage);
        this.solid                      = SOLID_CORPSE;
@@ -1033,7 +1041,6 @@ void Monster_Dead(entity this, entity attacker, float gibbed)
 
        CSQCModel_UnlinkEntity(this);
 
-       Monster mon = Monsters_from(this.monsterid);
        mon.mr_death(mon, this);
 
        if(this.candrop && this.weapon)
@@ -1297,6 +1304,12 @@ bool Monster_Spawn(entity this, bool check_appear, int mon_id)
        if(!(this.spawnflags & MONSTERFLAG_RESPAWNED))
        {
                IL_PUSH(g_monsters, this);
+               if(this.mins && this.maxs)
+               {
+                       this.oldmins = this.mins; // crude
+                       this.oldmaxs = this.maxs;
+               }
+
                if(this.mdl && this.mdl != "")
                        precache_model(this.mdl);
                if(this.mdl_dead && this.mdl_dead != "")
@@ -1389,7 +1402,10 @@ bool Monster_Spawn(entity this, bool check_appear, int mon_id)
                        this.scale *= 1.3;
        }
 
-       setsize(this, mon.mins * this.scale, mon.maxs * this.scale);
+       if(this.oldmins && this.oldmaxs)
+               setsize(this, this.oldmins * this.scale, this.oldmaxs * this.scale);
+       else
+               setsize(this, mon.mins * this.scale, mon.maxs * this.scale);
 
        this.ticrate = bound(sys_frametime, ((!this.ticrate) ? autocvar_g_monsters_think_delay : this.ticrate), 60);
 
index b667373a0f7926496b4633dc16dbc29e69d2b167..d0a5266d04f76b8a145de52f41e4604e62d30f22 100644 (file)
@@ -28,6 +28,8 @@ int monsters_killed;
 .int oldskin;
 .string mdl_dead; // dead model for goombas
 
+.vector oldmins, oldmaxs;
+
 #define MONSTER_SKILLMOD(mon) (0.5 + mon.monster_skill * ((1.2 - 0.3) / 10))
 
 // other properties