float rune_numspawns; float RUNE_FIRST = 1; float RUNE_STRENGTH = 1; float RUNE_DEFENSE = 2; float RUNE_REGEN = 4; float RUNE_SPEED = 8; float RUNE_VAMPIRE = 16; float RUNE_LAST = 16; float CURSE_FIRST = 8192; float CURSE_WEAK = 8192; float CURSE_VULNER = 16384; float CURSE_VENOM = 32768; float CURSE_SLOW = 65536; float CURSE_EMPATHY = 131072; float CURSE_LAST = 131072; float RUNE_COUNT = 5; /* rune ideas: Doom/Death Rune: When you damage enemies, you have a slight chance of instant-killing them (porportional to damage dealt / their health) Curse: When you are damaged, you have a chance of being instant-killed Vengence/Slothful Rune: The lower your health below 100, the more damage you deal (does not decrease your damage if you're above 100) Curse: The higher your health (up to 100), the less damage you deal (at 100 hp deal 1/5th damage) */ /*QUAKED spawnfunc_runematch_spawn_point (1 0 0) (-16 -16 -24) (16 16 24) spawn point for runes in runematch */ void spawnfunc_runematch_spawn_point() { if(!g_runematch || !cvar("g_runematch_fixedspawns")) { remove(self); return; } setsize(self, '0 0 -35', '0 0 0'); droptofloor(); ++rune_numspawns; } // only used if using rune spawns at all entity rune_find_spawnpoint() { entity e; if(rune_numspawns < RUNE_COUNT) return world; RandomSelection_Init(); for(e = world; (e = find(e, classname, "runematch_spawn_point")); ) if(e.owner == world) RandomSelection_Add(e, 0, string_null, e.cnt, 0); return RandomSelection_chosen_ent; } float rune_spawn_somewhere(entity e) { entity spot; spot = rune_find_spawnpoint(); if(spot) { spot.owner = e; setorigin(e, spot.origin); e.owner = spot; spot.owner = e; return TRUE; } else { if(MoveToRandomMapLocation(e, DPCONTENTS_SOLID | DPCONTENTS_CORPSE | DPCONTENTS_PLAYERCLIP, DPCONTENTS_SLIME | DPCONTENTS_LAVA | DPCONTENTS_SKY | DPCONTENTS_BODY | DPCONTENTS_DONOTENTER, Q3SURFACEFLAG_SKY, 10, 1024, 256)) { // great makevectors(self.angles), self.velocity = v_forward * 250; self.angles = '0 0 0'; return TRUE; } else { // sorry, can't spawn, better luck next frame return FALSE; } } } void rune_unmark_spot(entity e) { if(e.owner.classname == "runematch_spawn_point") { e.owner.owner = world; e.owner = world; } } string RuneName(float r) { if(r == RUNE_STRENGTH) return "^1Strength^7"; if(r == RUNE_DEFENSE) return "^4Defense^7"; if(r == RUNE_REGEN) return "^2Vitality^7"; if(r == RUNE_SPEED) return "^3Speed^7"; if(r == RUNE_VAMPIRE) return "^6Vampire^7"; if(r == CURSE_WEAK) return "^1Weakness^7"; if(r == CURSE_VULNER) return "^4Vulnerability^7"; if(r == CURSE_VENOM) return "^2Venom^7"; if(r == CURSE_SLOW) return "^3Slow^7"; if(r == CURSE_EMPATHY) return "^6Empathy^7"; return strcat("^8[unnamed", ftos(r), "]^7"); } vector RuneColormod(float r) { vector _color; if(r == RUNE_STRENGTH) _color = '255 0 0'; if(r == RUNE_DEFENSE) _color = '0 0 255';//'0 102 255';// if(r == RUNE_REGEN) _color = '0 204 0';//'0 255 0'; if(r == RUNE_SPEED) _color = 0.35*'185 185 0';//255 230 0';//'255 255 0'; if(r == RUNE_VAMPIRE) _color = '64 0 128';//'108 0 217';//'128 0 255';//'179 0 204';// if(r == CURSE_WEAK) _color = '255 0 0'; if(r == CURSE_VULNER) _color = '0 0 255';//'0 102 255';// if(r == CURSE_VENOM) _color = '0 204 0';//'0 255 0'; if(r == CURSE_SLOW) _color = 0.5*'185 185 0';//'255 255 0'; if(r == CURSE_EMPATHY) _color = '179 0 204';//'128 0 255'; return _color * (1 / 255) * cvar("g_runematch_rune_color_strength"); } void rune_respawn(); void RuneCarriedThink() { float rcount, rnum; vector ang; entity rune; if(self.owner.classname != "player" || time < game_starttime) { rune_respawn(); return; } self.nextthink = time + 0.1; // count runes my owner holds rcount = 0; rune = find(world, classname, "rune"); while(rune) { if(rune.owner == self.owner) rcount = rcount + 1; if(rune == self) rnum = rcount; rune = find(rune, classname, "rune"); } ang_y = rnum*(360 / rcount) + mod(time, 360)*45;//180; makevectors(ang); setorigin(self, v_forward*32); } void rune_touch() { if(trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT) { self.think = rune_respawn; self.nextthink = time; return; } if(other.classname != "player" || other.health < 1) return; if(self.wait > time) return; // "notouch" time isn't finished // detach from the spawn point you're on rune_unmark_spot(self); self.owner = other; self.enemy.owner = other; setattachment(self, other, ""); other.runes = other.runes | self.runes | self.enemy.runes; //self.think = SUB_Null; //self.nextthink = 0; self.think = RuneCarriedThink; self.nextthink = time; self.touch = SUB_Null; self.solid = SOLID_NOT; setorigin(self, self.origin); //sprint(other, strcat("^3You have picked up ", // RuneName(self.runes & (RUNE_LAST*2-1)), " and ")); //sprint(other, strcat(RuneName(self.enemy.runes & (CURSE_WEAK | CURSE_VULNER | CURSE_VENOM | CURSE_SLOW | CURSE_EMPATHY)), "\n")); bprint("^3", other.netname, "^7 has picked up ", RuneName(self.runes & (RUNE_LAST*2-1)), "^7 and "); bprint(RuneName(self.enemy.runes & (CURSE_WEAK | CURSE_VULNER | CURSE_VENOM | CURSE_SLOW | CURSE_EMPATHY)), "\n"); } void rune_respawn() { rune_unmark_spot(self); if(rune_spawn_somewhere(self)) { self.solid = SOLID_TRIGGER; self.touch = rune_touch; self.think = rune_respawn; self.nextthink = time + cvar("g_runematch_shuffletime");//30 + random()*5; // fixme: cvar } else { // try again later self.think = rune_respawn; self.nextthink = time; } } entity FindRune(entity own, string clname, float r) { entity rune; float _count, c; c = _count = 0; rune = world; do { rune = find(rune, classname, clname); if(!rune) rune = find(rune, classname, clname); if(!rune) break; if(rune.owner == own) { _count = _count + 1; if(_count >= r) return rune; if(r <= 1) return rune; } c = c + 1; }while(c < 30); return world; } void DropRune(entity pl, entity e) { //entity pl; //pl = e.owner; // detach from player setattachment(e, world, ""); e.owner = world; e.enemy.owner = world; // don't instantly touch player again e.wait = time + 1; // "notouch" time e.movetype = MOVETYPE_TOSS; e.solid = SOLID_TRIGGER; // reposition itself if not picked up soon e.think = rune_respawn; e.nextthink = time + cvar("g_runematch_respawntime");//15 + random()*5; // fixme: cvar e.touch = rune_touch; pl.runes = pl.runes - (pl.runes & (e.runes | e.enemy.runes)); // toss from player setorigin(e, pl.origin + '0 0 10'); e.velocity = '0 0 200' + '0 100 0'*crandom() + '100 0 0'*crandom(); bprint("^3", pl.netname, "^7 has lost ", RuneName(e.runes & (RUNE_LAST*2-1)), "^7 and "); bprint(RuneName(e.enemy.runes & (CURSE_WEAK | CURSE_VULNER | CURSE_VENOM | CURSE_SLOW | CURSE_EMPATHY)), "\n"); } float RuneMatchesCurse(float r, float c) { float cr; if(r & RUNE_STRENGTH) cr = CURSE_WEAK; else if(r & RUNE_DEFENSE) cr = CURSE_VULNER; else if(r & RUNE_REGEN) cr = CURSE_VENOM; else if(r & RUNE_SPEED) cr = CURSE_SLOW; else if(r & RUNE_VAMPIRE) cr = CURSE_EMPATHY; else return FALSE; // fixme: error? if(c & cr) return TRUE; return FALSE; } // player died, drop runes // each rune should pair up with a random curse and then be tossed from the player void DropAllRunes(entity pl) { entity rune, curse; float rcount, ccount, r, c, rand, prevent_same, numtodrop, tries; entity curse1, rune1, curse2, rune2; rune = curse = world; rcount = ccount = r = c = 0; rune = find(rune, classname, "rune"); while(rune) { if(rune.owner == pl) rcount = rcount + 1; rune = find(rune, classname, "rune"); } curse = find(curse, classname, "curse"); while(curse) { if(curse.owner == pl) ccount = ccount + 1; curse = find(curse, classname, "curse"); } numtodrop = cvar("g_runematch_drop_runes_max"); prevent_same = !cvar("g_runematch_allow_same"); rune = curse = world; do { rune = find(rune, classname, "rune"); if(!rune) break; if(rune.owner != pl) continue; // find a random curse tries = 15; if(ccount > 1 && prevent_same) { // avoid pairing runes and curses that match each other do{ rand = floor(random()*ccount) + 1; curse = FindRune(pl, "curse", rand); tries = tries - 1; }while(RuneMatchesCurse(rune.runes, curse.runes) && tries > 0); if(tries <= 0) { bprint("warning: couldn't prevent same rune\n"); } } else { rand = floor(random()*ccount) + 1; curse = FindRune(pl, "curse", rand); } if(!curse) error("Couldn't fine curse to bind rune to\n"); // pair rune and curse rune1 = rune; curse1 = curse; rune2 = curse1.enemy; curse2 = rune1.enemy; if(rune1 != rune2) // not already attached to each other { rune1.enemy = curse1; curse1.enemy = rune1; setattachment(curse1, rune1, ""); rune2.enemy = curse2; curse2.enemy = rune2; setattachment(curse2, rune2, ""); //DropRune(pl, rune2); //ccount = ccount - 1; //rcount = rcount - 1; } DropRune(pl, rune1); if(numtodrop <=0) { rune1.think = rune_respawn; rune1.nextthink = time; } numtodrop = numtodrop - 1; ccount = ccount - 1; rcount = rcount - 1; }while(rune); } void rune_reset() { if(self.owner) if(self.owner.classname != "runematch_spawn_point") DropAllRunes(self.owner); rune_respawn(); } void spawn_runes() { float rn, cs, runes_used, curses_used, prevent_same, numrunes; entity e; if(self) remove(self); // fixme: instead of placing them all now, why not // simply create them all and let them call rune_respawn() as their think? runes_used = 0; curses_used = 0; prevent_same = !cvar("g_runematch_allow_same"); numrunes = RUNE_COUNT; while(numrunes > 0) { RandomSelection_Init(); for(rn = RUNE_FIRST; rn <= RUNE_LAST; rn *= 2) if not(runes_used & rn) RandomSelection_Add(world, rn, string_null, 1, 1); rn = RandomSelection_chosen_float; RandomSelection_Init(); for(cs = CURSE_FIRST; cs <= CURSE_LAST; cs *= 2) if not(curses_used & cs) if not(prevent_same && cs == RuneMatchesCurse(rn, cs)) RandomSelection_Add(world, cs, string_null, 1, 1); cs = RandomSelection_chosen_float; if(!rn || !cs) error("No rune/curse left"); runes_used |= rn; curses_used |= cs; e = spawn(); e.runes = rn; e.classname = "rune"; e.touch = rune_touch; e.think = rune_respawn; e.nextthink = time; e.movetype = MOVETYPE_TOSS; e.solid = SOLID_TRIGGER; e.flags = FL_ITEM; e.reset = rune_reset; setmodel(e, "models/runematch/rune.mdl"); // precision set below setsize(e, '0 0 -35', '0 0 0'); e.enemy = spawn(); e.enemy.enemy = e; e.enemy.classname = "curse"; e.enemy.runes = cs; //e.enemy.avelocity = '300 500 200'; setmodel(e.enemy, "models/runematch/curse.mdl"); // precision set below setorigin(e, '0 0 0'); setattachment(e.enemy, e, ""); e.colormod = RuneColormod(rn); e.enemy.colormod = RuneColormod(cs); e.alpha = e.enemy.alpha = cvar("g_runematch_rune_alpha");//0.78; e.effects = e.enemy.effects = cvar("g_runematch_rune_effects") | EF_LOWPRECISION;//EF_ADDITIVE;// | EF_FULLBRIGHT; //e.glow_size = e.enemy.glow_size = cvar("g_runematch_rune_glow_size"); //e.glow_color = e.enemy.glow_color = cvar("g_runematch_rune_glow_color"); //rn = RUNE_FIRST; //cs = CURSE_FIRST; numrunes = numrunes - 1; } } void runematch_init() { if(!g_runematch) return; entity e; e = spawn(); e.think = spawn_runes; e.nextthink = time + 0.1; } float runematch_point_time; // give points to players who are holding runes void RuneMatchGivePoints() { entity rune; if(!g_runematch || !cvar("g_runematch_pointamt")) return; if(gameover) return; if(runematch_point_time > time) return; runematch_point_time = time + cvar("g_runematch_pointrate"); rune = world; do { rune = find(rune, classname, "rune"); if(!rune) return; if(rune.owner.classname == "player") { UpdateFrags(rune.owner, cvar("g_runematch_pointamt")); } }while(rune); } float RunematchHandleFrags(entity attacker, entity targ, float f) { entity head; float arunes, trunes, newfrags; if(f <= 0) return f; if(attacker == targ) return f; arunes = trunes = 0; head = find(world, classname, "rune"); while(head) { if(head.owner == attacker) { arunes = arunes + 1; } else if(head.owner == targ) { trunes = trunes + 1; } head = find(head, classname, "rune"); } if(!arunes && !trunes) return f - 1 + cvar("g_runematch_frags_norune"); // don't give points to players when no runes are involved. if(arunes) { // got a kill while holding runes newfrags = newfrags + cvar("g_runematch_frags_killedby_runeholder");//5; } if(trunes) { // killed an enemy holding runes newfrags = newfrags + cvar("g_runematch_frags_killed_runeholder");//5; } if(newfrags) f = f - 1 + newfrags; return f; }