// TMW2 scripts. // Authors: // Jesusalva // Description: // Town Siege utilities // Siege Spawn // Can be used anywhere to spawn on the whole map, margins respected. // siege_spawn( map, mobID, Amount, eventID ) function script siege_spawn { .@mp$=getarg(0); .@mid=getarg(1); .@qnt=getarg(2); .@ev$=getarg(3); areamonster(.@mp$, 20, 20, getmapinfo(MAPINFO_SIZE_X, .@mp$)-20, getmapinfo(MAPINFO_SIZE_Y, .@mp$)-20, strmobinfo(1, .@mid), .@mid, .@qnt, .@ev$); return; } // Calculate player average level // if highest is set, it will return highest player level, with minimum the value // passed. (A level "0" is clearly not valid, of course) // siege_calcdiff ( map{, highest_lvl} ) function script siege_calcdiff { .@bsum=0; .@highest=getarg(1, false); .@c = getunits(BL_PC, .@players, false, getarg(0)); .@skip=0; // There is at least one player, do things properly for (.@i = 0; .@i < .@c; .@i++) { /* // Dead players are not counted if (ispcdead(strcharinfo(0, "", .@players[.@i]))) continue; */ .@b=readparam(BaseLevel, .@players[.@i]); // GMs are not counted if (getgroupid(.@players[.@i]) >= 60) { .@skip+=1; .@b=0; } .@bsum+=.@b; if (.@b > .@highest) .@highest=.@b; } // Sanitize and fallback if needed .@c-=.@skip; if (!.@c) .@c=1; //debugmes "calcdiff: Total %d Average %d Highest %d", .@bsum, (.@bsum/.@c), .@highest; if (getarg(1,false)) return .@highest; else return (.@bsum/.@c); } // push to $@SIEGE_TMPMOBS the if their level is within a x levels range // above or below // siege_push ( mobID, {level{, variation}} ) function script siege_push { .@mi=getarg(0); .@lv=getarg(1,0); .@var=getarg(2,10); // Old Default: 15 if ( is_between(.@lv-.@var, .@lv+(.@var/2), strmobinfo(3, .@mi)) ) { //debugmes "Monster %s (%d) level %d ~= %d", strmobinfo(1, .@mi), .@mi, strmobinfo(3, .@mi), .@lv; array_push($@SIEGE_TMPMOBS, .@mi); } return; } // Selects a monster based on player average strength and base difficulty // It'll select monsters from 20 levels below to 20 levels above // tp_mask is the same as TP_* constants and changes mobs present. Only TP_TULIM, // TP_HURNS and TP_NIVAL are supported. You can use them with the "|" operand, eg., // TP_TULIM | TP_HURNS. // // It currently only creates $@SIEGE_TMPMOBS, you need to use any_of() manually. // siege_selectmob ( blvl, difficulty{, tp_mask} ) function script siege_selectmob { .@blv=getarg(0); .@dif=getarg(1, 0); .@tp=getarg(2, 0); // We don't need .@dif, so we convert difficulty to levels .@blv+=(.@dif/3); deletearray $@SIEGE_TMPMOBS; setarray $@SIEGE_TMPMOBS, ManaGhost, CandiedSlime, Bif, SlimeBlast; // Now we must select mobs, using array_push() to $@SIEGE_TMPMOBS // First, mobs on all envs siege_push(BlackScorpion, .@blv); siege_push(GreenSlime, .@blv); siege_push(CaveMaggot, .@blv); siege_push(MagicGoblin, .@blv); siege_push(AngryBat, .@blv); siege_push(BlackSlime, .@blv); siege_push(BlackScorpion, .@blv); siege_push(Forain, .@blv); siege_push(Terranite, .@blv); siege_push(JackO, .@blv); siege_push(BlackMamba, .@blv); siege_push(TerraniteProtector, .@blv); siege_push(Reaper, .@blv); // What if we are trying to select a boss and they're... overleveled? // We need a level 105, 120 and a level 135 monsters siege_push(FallenKing2, .@blv); siege_push(TerraniteKing, .@blv); // Now, mobs on only certain envs if (.@tp & TP_TULIM) { siege_push(AngryScorpion, .@blv); siege_push(AngryRedScorpion, .@blv); siege_push(DesertBandit, .@blv); siege_push(LavaSlime, .@blv); siege_push(OldSnake, .@blv); siege_push(Snake, .@blv); } if (.@tp & TP_HURNS) { siege_push(RedSlime, .@blv); siege_push(Bandit, .@blv); siege_push(RedMushroom, .@blv); siege_push(RobinBandit, .@blv); siege_push(AngryYellowSlime, .@blv); siege_push(GrassSnake, .@blv); siege_push(WickedMushroom, .@blv); siege_push(GreenDragon, .@blv); } if (.@tp & TP_NIVAL) { siege_push(Bluepar, .@blv); siege_push(Wolvern, .@blv); siege_push(Yeti, .@blv); siege_push(Moggun, .@blv); // PS. Not aggressive } // Removed //siege_push(DarkLizard, .@blv); //siege_push(Crafty, .@blv); return; } ///////////////////////////////////////////////////////////// // Prepare a siege with optional announce // siege_setup ( map ) function script siege_setup { .@m$=getarg(0); // Save old map zone $@MZONE$[getmapinfo(MAPINFO_ID, .@m$)]=getmapinfo(MAPINFO_ZONE, .@m$); // Apply changes addmapmask .@m$, MASK_MATTACK; changemusic .@m$, any("mythica.ogg", "eric_matyas_ghouls.ogg", "misuse.ogg", "Arabesque.ogg"); disablenpc("Mana Stone"); pvpon(.@m$); setmapflag(.@m$,mf_zone,"MMO"); // MMO Zone: Overrides GM Commands setmapflag(.@m$,mf_bexp,rand(120,140)); // 20~40% EXP UP on siege maps return; } // Check if boss was killed or not // siege_check ( map ) function script siege_check { .@m$=getarg(0); .@mb=0; .@mb+=mobcount(.@m$, "#SiegeCtrl::OnSergeantDeath"); .@mb+=mobcount(.@m$, "#SiegeCtrl::OnLieutenantDeath"); .@mb+=mobcount(.@m$, "#SiegeCtrl::OnCaptainDeath"); .@mb+=mobcount(.@m$, "#SiegeCtrl::OnColonelDeath"); .@mb+=mobcount(.@m$, "#SiegeCtrl::OnGeneralDeath"); // Players failed, so reduce score in 1 if (.@mb) { $MK_TEMPVAR-=1; kamibroadcast("Players failed to defend the city!!"+.@mb); } return; } // Revert what siege_setup did // siege_revert ( map ) function script siege_revert { .@m$=getarg(0); // Revert map zone (to town, or to blank) and delete backup removemapflag(.@m$,mf_zone); //setmapflag(.@m$,mf_zone,$@MZONE$[getmapinfo(MAPINFO_ID, .@m$)]); setmapflag(.@m$,mf_zone,"Normal"); // Broken for some reason setmapflag(.@m$,mf_zone,"All"); $@MZONE$[getmapinfo(MAPINFO_ID, .@m$)]=""; removemapmask .@m$, MASK_MATTACK; changemusic .@m$, "caketown.ogg"; // :> enablenpc("Mana Stone"); pvpoff(.@m$); removemapflag(.@m$,mf_bexp); removemapflag(.@m$,mf_nosave); setmapflag(.@m$,mf_bexp,100); killmonsterall(.@m$); return; } // Create the Siege Boss for #SiegeCtrl utility, DO NEVER CAST TWICE // siege_boss ( map, difficulty ) function script siege_boss { .@m$=getarg(0); .@s=getarg(1,0); // If Difficulty is 0: There is no boss if (!.@s) return; /* Uncomment this to use common monsters as staff // We will now prepare the boss // It must be stronger than players in at least 15 levels, so the mob group // is different. The siege difficulty, as usual, gives an extra level to them. .@val=siege_calcdiff(.@m$)+15; .@val+=.@s; // We must cap this at 100 to prevent running out of monsters // Also, .@val can NEVER be less than 50, to prevent cheating :< if (.@val > 100) .@val=100; else if (.@val < 50) .@val=50; // Get their event/designation if (.@val >= 80) .@ts$="Colonel"; else .@ts$="Lieutenant"; // Select a type for them (saved as .@mobId) siege_selectmob(siege_calcdiff(.@m$, .@val), .@s); array_remove($@SIEGE_TMPMOBS, Bif); array_remove($@SIEGE_TMPMOBS, CandiedSlime); array_remove($@SIEGE_TMPMOBS, ManaGhost); array_remove($@SIEGE_TMPMOBS, SlimeBlast); if (array_entries($@SIEGE_TMPMOBS) > 0) { .@mobId=any_of($@SIEGE_TMPMOBS); } else { .@mobId=Yetifly; Exception("[WARNING] Insufficient monsters in database: Highlight @jesusalva - Broken reference: "+siege_calcdiff(.@m$, .@val), RB_DEBUGMES|RB_IRCBROADCAST); } */ // We now select based on player average level and a seed of randomness .@val=siege_calcdiff(.@m$)+.@s; // Nobody is on map: Be TRULY random if (.@val < 10) { .@val=rand(20,97); } // Switch an adequate boss, almost always stronger if (.@val <= 20) { .@mobId=MonsterSergeant; .@ts$="Sergeant"; } else if (.@val <= 40) { .@mobId=MonsterLieutenant; .@ts$="Lieutenant"; } else if (.@val <= 60) { .@mobId=MonsterCaptain; .@ts$="Captain"; } else if (.@val <= 80) { .@mobId=MonsterColonel; .@ts$="Colonel"; } else { .@mobId=MonsterGeneral; .@ts$="General"; } // We want spawn point to be fixed .@lx=array_find($@LOCMASTER_MAP$, .@m$); .@xm=$@LOCMASTER_X[.@lx]; .@ym=$@LOCMASTER_Y[.@lx]; .@xm=.@xm+rand(-1,1); .@ym=.@ym+rand(-1,1); // Announce and spawn .@mg=monster(.@m$, .@xm, .@ym, strmobinfo(1, .@mobId), .@mobId, 1, "#SiegeCtrl::On"+.@ts$+"Death"); // Boost the boss stats based on difficulty and nÂș of players online .@bhp=getunitdata(.@mGID, UDT_MAXHP); .@bat=getunitdata(.@mGID, UDT_ATKMAX); .@bdf=getunitdata(.@mGID, UDT_DEF); .@bcr=getunitdata(.@mGID, UDT_CRIT); .@s+=getusers(1); setunitdata(.@mg, UDT_MAXHP, .@bhp+(.@s*45)); setunitdata(.@mg, UDT_HP, .@bhp+(.@s*45)); setunitdata(.@mg, UDT_ATKMAX, .@bat+(.@s*5)); setunitdata(.@mg, UDT_DEF, .@bdf+(.@s*4)); setunitdata(.@mg, UDT_CRIT, .@bcr+(.@s*3)); // Spawn some scouts areamonster(.@m$, .@xm-1, .@ym-1, .@xm+1, .@ym+1, "Scout", any(GreenSlime,RedSlime,AngryYellowSlime), 2); areamonster(.@m$, .@xm-1, .@ym-1, .@xm+1, .@ym+1, "Scout", any(GreenSlime,RedSlime,AngryYellowSlime), 2); areamonster(.@m$, .@xm-1, .@ym-1, .@xm+1, .@ym+1, "Scout", any(GreenSlime,RedSlime,AngryYellowSlime), 2); announce("##1The Monster "+.@ts$+" arrived! Watch out!", bc_all); return; } // Spawn some monsters // siege_cast ( map, NPCName, {, difficulty{, tpflag}} ) function script siege_cast { // mz - map ; n - name ; d - difficulty ; tp - teleport // a - ammount ; e - mobId .@mz$=getarg(0); .@n$=getarg(1); .@d=getarg(2,0); .@tp=getarg(3,0); siege_selectmob(siege_calcdiff(.@mz$), .@d, .@tp); // How many monsters? This value is multiplied by 3; // 1 per player (= 3 mobs), 1 each 5 difficulty, 1 always present. .@a=getmapusers(.@mz$); .@a=(.@d/5)+1; .@e=any_of($@SIEGE_TMPMOBS); array_remove($@SIEGE_TMPMOBS, .@e); siege_spawn(.@mz$, .@e, .@a, "#SiegeCtrl::OnRespawn"); .@e=any_of($@SIEGE_TMPMOBS); array_remove($@SIEGE_TMPMOBS, .@e); siege_spawn(.@mz$, .@e, .@a, "#SiegeCtrl::OnRespawn"); .@e=any_of($@SIEGE_TMPMOBS); array_remove($@SIEGE_TMPMOBS, .@e); siege_spawn(.@mz$, .@e, .@a, "#SiegeCtrl::OnRespawn"); return; } //////////////////////////////////////////// // Utility Function // do_siege ( town, outskirts, varcode, flag, npc, timer ) function script do_siege { .@m$=getarg(0); .@o$=getarg(1); .@c$=getarg(2); .@tp=getarg(3); .@n$=getarg(4); .@t=getarg(5); switch (.@t) { // Warmup case 70: case 95: siege_cast(.@m$, .@n$, getd("$@SIEGE_"+.@c$), .@tp); case 35: siege_cast(.@o$, .@n$, getd("$@SIEGE_"+.@c$), .@tp); break; // Setup and casts case 0: siege_setup(.@o$); siege_cast("014-3", .name$, 0, TP_HURNS); break; case 60: siege_setup(.@m$); siege_cast(.@o$, .@n$, 0, .@tp); mapannounce(.@m$, "##2Message to all NPCs in town: Take shelter!", bc_map); break; case 90: siege_cast(.@m$, .@n$, getd("$@SIEGE_"+.@c$), .@tp); siege_cast(.@o$, .@n$, getd("$@SIEGE_"+.@c$), .@tp); break; // Boss stage case 280: siege_boss(.@m$, getd("$@SIEGE_"+.@c$)); siege_cast(.@m$, .@n$, getd("$@SIEGE_"+.@c$), .@tp); break; // Difficulty Raisers case 310: case 520: case 640: .@varsig=getd("$@SIEGE_"+.@c$); setd("$@SIEGE_"+.@c$, .@varsig+1); // Regular flow case 195: case 220: case 265: //case 280: BOSS WAVE //case 310: difficulty raiser case 355: case 400: case 445: case 490: //case 520: difficulty raiser case 535: case 580: case 625: //case 640: difficulty raiser case 670: siege_cast(.@m$, .@n$, getd("$@SIEGE_"+.@c$), .@tp); break; // Ending flow // TODO: It would be better to make these values relative to MK_SIEGE_DURATION case 700: mapannounce("012-1", "##1The Monster Army is planning to retreat soon!", bc_map); siege_cast(.@m$, .@n$, getd("$@SIEGE_"+.@c$), .@tp); break; case 760: mapannounce("012-1", "##1The Monster Army is withdrawing within 30 seconds!", bc_map); $@MK_SCENE=MK_NONE; $@MK_AGGRO=$@MK_AGGRO/20; break; // Check db/constants.conf to change this value! case MK_SIEGE_DURATION: siege_check(.@m$); siege_revert(.@m$); siege_revert(.@o$); enablenpc("Mana Stone"); setd("$@SIEGE_"+.@c$, 0); break; } return; } // Utility NPC - script #SiegeCtrl NPC_HIDDEN,{ end; OnRespawn: if (playerattached()) { getmapxy(.@m$,.@x,.@y,0); if (rand(10000) <= $coinsrate) makeitem StrangeCoin, 1, .@m$, .@x, .@y; } end; // Boss Death Labels OnSergeantDeath: $MK_TEMPVAR+=1; getitem StrangeCoin, rand(1,5); announce("##2The Monster Sergeant was defeated by "+strcharinfo(0)+"!", bc_all); end; OnLieutenantDeath: $MK_TEMPVAR+=3; getitem StrangeCoin, rand(5,10); announce("##2The Monster Lieutenant was defeated by "+strcharinfo(0)+"!", bc_all); end; OnCaptainDeath: $MK_TEMPVAR+=5; getitem StrangeCoin, rand(10,15); announce("##2The Monster Captain was defeated by "+strcharinfo(0)+"!", bc_all); end; OnColonelDeath: $MK_TEMPVAR+=7; getitem StrangeCoin, rand(15,20); $MOST_HEROIC$=strcharinfo(0); announce("##2The Monster Colonel was defeated by "+strcharinfo(0)+"!", bc_all); end; OnGeneralDeath: $MK_TEMPVAR+=9; getitem StrangeCoin, rand(20,25); $MOST_HEROIC$=strcharinfo(0); announce("##2The Monster General was defeated by "+strcharinfo(0)+"!", bc_all); end; }