// 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 // flag 1 - Don't count dead players // flag 2 - Return highest player level, instead of average. // flag 4 - Return the total sum of levels instead. // siege_calcdiff ( map{, flags} ) function script siege_calcdiff { .@bsum=0; if (getarg(1,0) & 1) .@deadcount=true; if (getarg(1,0) & 2) .@onlyhighest=true; if (getarg(1,0) & 4) .@onlytotal=true; .@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 (.@deadcount) { if (ispcdead(strcharinfo(0, "", .@players[.@i]))) { .@skip+=1; 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 (.@onlyhighest) return .@highest; else if (.@onlytotal) return .@bsum; 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,14); // Old Default: 15, and then 10 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/any(2,2,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 if (getmapinfo(MAPINFO_ID, .@m$) < 1) { return Exception("SIEGE ERROR, INVALID MAP ID: "+.@m$, RB_ISFATAL|RB_DEBUGMES|RB_IRCBROADCAST|RB_GLOBALANNOUNCE); } //$@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"); if (.@m$ != "003-1") pvpon(.@m$); setmapflag(.@m$,mf_zone,"MMO"); // MMO Zone: Overrides GM Commands setmapflag(.@m$,mf_bexp,rand2(135,142)); // 35~42% 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~5 (like Sergeant~General). // In future, it could be inverse proportion (-9 for sergeant, -1 for general) if (.@mb) { if ($GAME_STORYLINE == 2) $MK_TEMPVAR-=rand2(1, 5); kamibroadcast("Players failed to defend the city!!"); debugmes "Number of boss grade monsters found: %d", .@mb; $SIEGE_DIFFICULTY=max(1, ($SIEGE_DIFFICULTY/2)); // Lower the town exports in 5% (but never more than 25 GP) .@var$="$"+strtoupper(MapToLoc(.@m$))+"_EXPORT"; .@pen=min(25, getd(.@var$)/20); setd(.@var$, getd(.@var$)-.@pen); } else { kamibroadcast("The city was defended with success! GG, everyone!"); $SIEGE_DIFFICULTY+=1; } 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,"Normal2"); // Normal doesn't works... //setmapflag(.@m$,mf_zone,"All"); //$@MZONE$[getmapinfo(MAPINFO_ID, .@m$)]=""; removemapmask .@m$, MASK_MATTACK; changemusic .@m$, "caketown.ogg"; // :> 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); // 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)+.@s; } // 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(.@mg, UDT_MAXHP); .@bat=getunitdata(.@mg, UDT_ATKMAX); .@bdf=getunitdata(.@mg, UDT_DEF); .@bcr=getunitdata(.@mg, UDT_CRIT); .@s+=getusers(1); setunitdata(.@mg, UDT_MAXHP, .@bhp+(.@s*250)+.@val*5*getmapusers(.@m$)); setunitdata(.@mg, UDT_HP, .@bhp+(.@s*250)+.@val*5*getmapusers(.@m$)); 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); // Difficulty doesn't applies to Tulimshar if (.@mz$ == "003-1") .@d=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); // Dry run if ($@SIEGE_ABORTED) return; // If no active player, KILL THE SCRIPT .@c = getunits(BL_PC, .@players, MAX_CYCLE_PC); .@idle = 0; for (.@i = 0; .@i < .@c; .@i++) { attachrid(.@players[.@i]); if (checkidle() < 450) .@idle++; detachrid(); } // No one is active, cancel the event if (!.@idle) { kamibroadcast(col(b("EVENT CANCELLED DUE TO PLAYER INACTIVITY"),1)); $@MK_AGGRO=$@MK_AGGRO/5; // Lower aggro bar to 20% // Cleanup and Garbage Collection siege_revert(.@m$); siege_revert(.@o$); enablenpc("Mana Stone"); setd("$@SIEGE_"+.@c$, 0); setd("$@SIEGE_ABORTED", true); return; } // In past, we had a $@SIEGE_ to determine difficulty // This behavior is now deprecated, we use a global $SIEGE_DIFFICULTY // Which raises in 1 every victory and lowers in 1 every defeat (capped at 1) // But this behavior can be overriden if (!getd("$@SIEGE_"+.@c$)) { .@difc=max(1, getd("$SIEGE_DIFFICULTY")); // And then, we reset $@SIEGE_ so it can be manipulated // And set .@difc to this value setd("$@SIEGE_"+.@c$, .@difc); } // Set difficulty based on previous value .@difc=getd("$@SIEGE_"+.@c$); switch (.@t) { // Warmup case 70: case 95: siege_cast(.@m$, .@n$, .@difc, .@tp); case 35: siege_cast(.@o$, .@n$, .@difc, .@tp); break; // Setup and casts case 0: siege_setup(.@o$); siege_cast(.@o$, .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$, .@difc, .@tp); siege_cast(.@o$, .@n$, .@difc, .@tp); break; // Boss stage case 280: siege_boss(.@m$, .@difc); siege_cast(.@m$, .@n$, .@difc, .@tp); break; // Difficulty Raisers case 310: case 520: case 640: .@varsig=.@difc; 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$, .@difc, .@tp); break; // Ending flow // TODO: It would be better to make these values relative to MK_SIEGE_DURATION case 700: mapannounce(.@m$, "##1The Monster Army is planning to retreat soon!", bc_map); siege_cast(.@m$, .@n$, .@difc, .@tp); break; case 760: mapannounce(.@m$, "##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: if ($GAME_STORYLINE == 2) $MK_TEMPVAR+=1; getitem StrangeCoin, rand2(1,5); announce("##2The Monster Sergeant was defeated by "+strcharinfo(0)+"!", bc_all); $@EXP_EVENT=rand2(1, 3); $@EXP_EVENT_TIME=1; donpcevent "@exprate::OnPlayerCall"; end; OnLieutenantDeath: if ($GAME_STORYLINE == 2) $MK_TEMPVAR+=3; getitem StrangeCoin, rand2(5,10); announce("##2The Monster Lieutenant was defeated by "+strcharinfo(0)+"!", bc_all); $@EXP_EVENT=rand2(4, 6); $@EXP_EVENT_TIME=1; donpcevent "@exprate::OnPlayerCall"; end; OnCaptainDeath: if ($GAME_STORYLINE == 2) $MK_TEMPVAR+=5; getitem StrangeCoin, rand2(10,15); announce("##2The Monster Captain was defeated by "+strcharinfo(0)+"!", bc_all); $@EXP_EVENT=rand2(7, 9); $@EXP_EVENT_TIME=1; donpcevent "@exprate::OnPlayerCall"; end; OnColonelDeath: if ($GAME_STORYLINE == 2) $MK_TEMPVAR+=7; getitem StrangeCoin, rand2(15,20); announce("##2The Monster Colonel was defeated by "+strcharinfo(0)+"!", bc_all); $@EXP_EVENT=rand2(10, 12); $@EXP_EVENT_TIME=1; donpcevent "@exprate::OnPlayerCall"; end; OnGeneralDeath: if ($GAME_STORYLINE == 2) $MK_TEMPVAR+=9; getitem StrangeCoin, rand2(20,25); announce("##2The Monster General was defeated by "+strcharinfo(0)+"!", bc_all); $@EXP_EVENT=rand2(13, 15); $@EXP_EVENT_TIME=1; donpcevent "@exprate::OnPlayerCall"; end; }