// TMW2 scripts. // Author: // Jesusalva // Description: // Moubootaur Legends :: Final Showdown // /** Checklist Global variable ($@ML_SHOWDOWN) 0 - Not started 1 - Set automatically once first player enters 2 - North warp enabled 3 - Introductory Cutscene Over (after the Moubootaur changes platform) "Hah, not bad! Maybe it's time to take this a bit more seriously!" 4 - Central Platform Disabled / Distraction ("Aeros Fall") 5 - Central Platform Enabled / Cutscene after Distraction is solved 6 - All Bets are Off Mode -> Maybe we can remove collisions; set a map mask and join the platforms -> So the mode 6 fight has more fighting space (and more ML hopping around) 7 - Final Cutscene and/or Cooldown Cassia Unlock the quest for everyone! (bgmaster.txt - remove Superior Safeguard) Western Warp & Platforms (#MLWA+) Cascate of Central Door Condition Warps/Slide are causing desync D: Central Platform Fishing Conditional In-Promptu Cooking with special recipe and fail chance (optional) (must equip) In-Promptu Crafting with special recipe and fail chance (optional) (must equip) Formulate a random chant based on random clues (optional) TMW-BR Christmas Bells challenge/miniquest (optional) Eastern Warp Motivation Mode Reduced aggro for Moubootaur Skills Targetting during Armies Mode Global Timers Defeat Detection (when is everything reset? Maybe a turn limit?) -> Maybe if there are 2,500 mobs in map, assume players failed? Death & Logout Handler Respawn point and Logout point (nosave, resave) -> Still not broadly tested Moubootaur Strong Debuffs: Legendary Weapons (D. Poison, D. Sleep, Confusion, ... ?) Moubootaur Anti-Homunculus/Merc Woe-On-Who-Doesn't Skill Map Modes ? (Can we add a map mode to reduce non-magical damage?) Cutscenes (use sshake) Final Cutscene External Playtesting Lots of bugs which supposedly were fixed Test Data -> Talk to AyashaDebug [+-] Players, full kit, max level, all counters checked [OK] Trait Selection Legendary Weapon Selection (while in stock) .bgBonus = 0; 0 = no bonus and no penalty positive = bonus to PLAYERS negative = bonus to MONSTER */ 001-15 mapflag zone FinalMMO // Do we need a battlegrounds MF? 001-15,0,0,0 script #Moubootaur NPC_HIDDEN,{ end; public function DeathHandler; public function ReviveHandler; // _calcDebuff ( length ) // A length of 0 will cause penalty to NOT be applied public function _calcDebuff { .@beat=getarg(0, 11); .@all_down=0; .@atk_down=0; .@vulnerab=0; .@imprecis=0; .@dumbhead=0; .@slowpoke=0; .@inspired=0; /* These are the penalties associated with YOU */ if (!getq(General_Milly)) { .@dumbhead+=5; if (!@ml_penwarn) dispbottom col(l("Your lack of understanding on the world penalizes you."), 1); } if (!YETIKING_WINNER) { .@vulnerab+=5; if (!@ml_penwarn) dispbottom col(l("Not toughned by the Yeti King, you suffer some vulnerability."), 1); } if (!HEROESHOLD_WINNER) { .@imprecis+=5; if (!@ml_penwarn) dispbottom col(l("Your lack of exploration in dungeons causes you difficulty to follow up enemies."), 1); } if (REBIRTH < 5) { .@slowpoke+=5; if (!@ml_penwarn) dispbottom col(l("Your mortal coil is inadequate, causing you slowdowns."), 1); } if (!QUIRINO_WINNER) { .@vulnerab+=5; if (!@ml_penwarn) dispbottom col(l("Not tampered by PvP, you feel your body weaker in this realm."), 1); } if (!MOUBOOTAUR_WINNER) { .@dumbhead+=10; .@atk_down+=10; if (!@ml_penwarn) dispbottom col(l("You don't know enough about Andrei Sakar, giving you a considerable penalty."), 1); } if (!GHQ_WINNER) { .@imprecis+=5; if (!@ml_penwarn) dispbottom col(l("You didn't kill enough monsters to know how to fight effectively."), 1); } if (!EPISODE_WINNER) { .@atk_down+=30; .@all_down+=25; .@vulnerab+=20; if (!@ml_penwarn) dispbottom col(l("You never received Elli's blessing. The sheer aura of this place scares you."), 1); } if (!MK_WINNER) { .@atk_down+=15; if (!@ml_penwarn) dispbottom col(l("Never having faced such disadvantageous odds before, you fail to bring out your full potential."), 1); } if (#BETA_REVIVE) { .@imprecis+=1; // Silent penalty } if (!countitem(JesusalvaGrimorium)) { .@dumbhead+=10; if (!@ml_penwarn) dispbottom col(l("You feel your knowledge increasingly amiss, if only you had brought a Grimorium with you."), 1); } if (PETER_REPEAT < 50 && getq2(HalinarzoQuest_LifeDelight) < 51 && getq2(FrostiaQuest_JhonH) < 500 && MERCENARY_DAILYQUEST < 500) { // Persistence is the key to victory! .@imprecis+=5; if (!@ml_penwarn) dispbottom col(l("Your lack of patience makes you feel too impatient to fight adequately."), 1); } if (bitmask_count(getq2(General_EasterEggs)) < 14) { .@all_down += 14 - bitmask_count(getq2(General_EasterEggs)); if (!@ml_penwarn) dispbottom col(l("You feel like you didn't saw enough Easter Eggs in this world yet."), 1); } if (getq3(TulimsharQuest_Swezanne) < 20) { .@slowpoke+=1; if (!@ml_penwarn) dispbottom col(l("Without Swezanne's support, you feel slightly heavy."), 1); } if (getq(TulimsharQuest_DarkInvocator) < 7) { .@dumbhead+=2; if (!@ml_penwarn) dispbottom col(l("Your lack of understanding of dark arts gives you a penalty."), 1); } if (getq(HalinarzoQuest_SickWife) < 5) { .@all_down+=1; if (!@ml_penwarn) dispbottom col(l("Never seeing how Elixir of Life is made, you feel weakened."), 1); } // Not solving Injuried Mouboo (either way) will cause atk_down if (getq(HurnscaldQuest_InjuriedMouboo) <= 2) { .@atk_down+=25; if (!@ml_penwarn) dispbottom col(l("Not knowing anything about the Moubootaur's curse, your attack is severely reduced."), 1); } if (!getq(LilitQuest_Access)) { .@slowpoke+=3; if (!@ml_penwarn) dispbottom col(l("Your lack of experience swimming causes you to be unable to cope with heavy air in this place."), 1); } if (getq(LoFQuest_Inspector) < 9) { .@dumbhead+=5; .@imprecis+=1; if (!@ml_penwarn) dispbottom col(l("Your lack of experience in investigations penalizes you."), 1); } if (BARBARA_STATE != 3) { .@imprecis+=1; // Silent penalty } // Not completing any of Cadis' requests (Over100 is likely done tho) if (getq(FortressQuest_SlimeHunter) < 2 && getq(FortressQuest_RangedHunt) < 2 && getq(FortressQuest_Over100) < 2) { .@imprecis+=1; .@all_down+=1; if (!@ml_penwarn) dispbottom col(l("You feel that if you had done Commander Cadis' absurd grind, you would be able to fight better."), 1); } // You should have something to eat before fighting! if (getequipid(EQI_SHADOW_ACC_L) < 1) { .@vulnerab+=1; .@imprecis+=1; .@slowpoke+=1; if (!@ml_penwarn) dispbottom col(l("You are starving. Maybe you should have cooked something beforehand."), 1); } // This one should be the last if (getq(NivalisQuest_Henry) < 2) { .@vulnerab+=3; if (.@atk_down) .@atk_down+=5; if (!@ml_penwarn) dispbottom col(l("You never really dealt with illegal drugs and strong poison, which affects you negatively."), 1); } // Homunculus dead or not present wastes part of ML kit, so... // FIXME: (This is a temporary placeholder, ML should hit players w/o homun) // Also, without this structure it errors and dies if (!gethominfo(0)) { .@atk_down+=1; if (!@ml_penwarn) dispbottom col(l("Without a homunculus active, you feel weaker."), 1); } else if (homstatus()) { .@atk_down+=1; if (!@ml_penwarn) dispbottom col(l("Without a homunculus active, you feel weaker."), 1); } /* These are the penalties associated with YOUR PARTY */ if (!.wh) { .@all_down+=1; .@slowpoke+=5; if (!@ml_penwarn) dispbottom col(l("You feel demotivated without the World Hero around!"), 1); } if (!.lb) { .@all_down+=10; if (!@ml_penwarn) dispbottom col(l("Your party is not protected by the Mana Source, greatly penalizing the team!"), 1); } if (!.da) { .@atk_down+=20; if (!@ml_penwarn) dispbottom col(l("Without Demure's great war cry, you feel unable to cause as much damage as you normally do!"), 1); } if (!.ty) { .@imprecis+=15; if (!@ml_penwarn) dispbottom col(l("Without Tyranny's guidance, you feel like you can't concentrate well!"), 1); } if (!.rs) { .@dumbhead+=10; if (!@ml_penwarn) dispbottom col(l("Without the Runestaff great feats, you feel your intelligence leaving you!"), 1); } if (!.as) { .@vulnerab+=15; .@slowpoke+=5; if (!@ml_penwarn) dispbottom col(l("Without a solid tanker, your legs start shaking!"), 1); } if (!.bb) { .@slowpoke+=1; if (!@ml_penwarn) dispbottom col(l("Your party wasn't quick enough to grab the weapon the Monster King left behind, and it shows!"), 1); } /* These are penalties associated with MAP */ .@factor = log2(.bgBonus) * ((1 + $@ML_SHOWDOWN) / 2); if (.bgBonus > 0) { .@inspired += limit(1, .@factor, 50); if (!@ml_penwarn) dispbottom col(l("Allied forces are winning in Eastern Front, inspiring you."), 3); } else if (.bgBonus < 0) { .@vulnerab += limit(1, .@factor, 50); if (!@ml_penwarn) dispbottom col(l("Allied forces are losing in Eastern Front, debuffing you."), 1); } // Notify about the penalties the first time (@ml_penwarn) @ml_penwarn=true; // If no duration was specified, do not apply anything. if (!.@beat) return; // Apply the penalties if (.@all_down) SC_Bonus(.@beat, SC_ALL_DOWN, .@all_down); if (.@atk_down) SC_Bonus(.@beat, SC_ATK_DOWN, .@atk_down); if (.@vulnerab) SC_Bonus(.@beat, SC_VULNERABLE, .@vulnerab); if (.@imprecis) SC_Bonus(.@beat, SC_IMPRECISE, .@imprecis); if (.@dumbhead) SC_Bonus(.@beat, SC_DUMBHEAD, .@dumbhead); if (.@slowpoke) SC_Bonus(.@beat, SC_SLOWPOKE, .@slowpoke); if (.@inspired) SC_Bonus(.@beat, SC_INSPIRED, .@inspired); return; } function checkPenalties { .@c=getunits(BL_PC, .@pcs, MAX_CYCLE_PC, "001-15"); .wh=false; .lb=false; .da=false; .ty=false; .rs=false; .as=false; .bb=false; // First loop is to determine the conditions for (.@i = 0; .@i < .@c; .@i++) { // Lets check if they qualify attachrid(.@pcs[.@i]); // Data Integrity Check .@wpn = getequipid(EQI_HAND_R); .@all = readparam2(bStr) + readparam2(bAgi) + readparam2(bVit) + readparam2(bInt) + readparam2(bLuk) + readparam2(bDex); // IT IS OVER 9000! In other words, your data is corrupted if (.@all > 9000) { warp "001-16", 90, 45; nude(); } // Trying to cheat, eh? if (Class == Skelli && .@wpn > 1) { percentheal 0, any(-1, -2, -3, -1); unequip(EQI_HAND_R); dispbottom l("The %s is too hot, or perhaps you are too cold, to use it adequately.", getitemname(.@wpn)); nude(); // TODO Check if this works soundeffect("ML/NoWeapon.ogg",0); } // Dead, so you do not qualify if (ispcdead() || Class == Skelli) { detachrid(); continue; } // World Hero Check if (strcharinfo(0) == $MOST_HEROIC$) .wh = true; // Legendary Weapon Check switch (.@wpn) { case Lightbringer: .lb=true; break; case DemureAxe: .da=true; break; case Tyranny: .ty=true; break; case Runestaff: .rs=true; break; case AegisShield: .as=true; break; case Blightbringer: .bb=true; break; } // Next one! detachrid(); } // Second loop is to apply penalties for (.@i = 0; .@i < .@c; .@i++) { // Lets check if they qualify attachrid(.@pcs[.@i]); if (ispcdead() || Class == Skelli) { SC_Bonus(60, SC_ELLIBAN, 1); detachrid(); continue; } if (getarg(0, -1) >= 0) _calcDebuff( getarg(0, 0) ); else _calcDebuff( ); detachrid(); } return; } function _boostMe { .@mg = getarg(0); .@bat=getunitdata(.@mg, UDT_ATKMAX); .@bai=getunitdata(.@mg, UDT_ATKMIN); .@bdf=getunitdata(.@mg, UDT_DEF); .@bcr=getunitdata(.@mg, UDT_CRIT); .@bag=getunitdata(.@mg, UDT_AGI); .@bf = getarg(1, $@ML_SHOWDOWN) + 3; .@s=.@bf+rand2(5); .@bat = .@bat * (.@bf / 2); .@bcr = .@bcr * (.@bf / 2); setunitdata(.@mg, UDT_ATKMAX, .@bat+(.@s*5)); setunitdata(.@mg, UDT_DEF, .@bdf+(.@s*4)); setunitdata(.@mg, UDT_CRIT, .@bcr+(.@s*3)); setunitdata(.@mg, UDT_ATKMIN, .@bai+(.@s*2)); setunitdata(.@mg, UDT_AGI, .@bag+(.@s*1)); return; } // _spawnMob(mobId, x1, y1, x2, y2, {name=default, friendly=no}) // Returns: MobID function _spawnMob { .@mob=areamonster("001-15", getarg(1), getarg(2), getarg(3), getarg(4), getarg(5, strmobinfo(1, getarg(0))), getarg(0), 1); // Make aggressive .@opt=getunitdata(.@mob, UDT_MODE); .@opt=.@opt|MD_AGGRESSIVE; setunitdata(.@mob, UDT_MODE, .@opt); // Make friendly (optional) if (getarg(6, false)) setunitdata(.@mob, UDT_AI, 1); // 1 = AI_ATTACK = attack/friendly // Return the mob ID, in case it'll get boosted return .@mob; } // MapMode( enable=true ) public function MapMode { // Define the planned new map mode .@new_mode = rand2(10); // Revert the old map mode removemapmask "001-15", .mapMode; switch (.mapMode) { case ML_MAPMODE_PVP: pvpoff("001-15"); break; case ML_MAPMODE_NOCHAT: removemapflag("001-15",mf_nocommand); removemapflag("001-15",mf_nochat); break; case ML_MAPMODE_NOMAGIC: setmapflag(.@m$,mf_zone,"FinalMMO"); break; case ML_MAPMODE_NOPETS: removemapflag("001-15",mf_nopet); break; default: break; } // Takes one optional argument, to disable mode if (!getarg(0, true)) return 0; // Set the new map mode switch (.@new_mode) { case ML_MAPMODE_PVP: pvpon("001-15"); break; case ML_MAPMODE_NOCHAT: setmapflag("001-15",mf_nocommand,true); setmapflag("001-15",mf_nochat,true); break; case ML_MAPMODE_NOMAGIC: setmapflag(.@m$,mf_zone,"FinalMMO No Tricks"); break; case ML_MAPMODE_NOPETS: setmapflag("001-15",mf_nopet,true); break; default: break; } // Save the new map mode .mapMode = .@new_mode; addmapmask "001-15", .@new_mode; return .mapMode; } OnVictory: // Generic Prize Mobpt += 150000; getitem StrangeCoin, 250; getexp 10000000, 100000; // 10M, 100K, but you're likely maxed already AETHYR_PTS+=10000; // 10k, equivalent to 7 rares // First Victory Prize if (!FINAL_WINNER) { FINAL_WINNER=($@ML_START ? $@ML_START : gettimetick(2)); getitembound MoubootaurHead, 1, 1; getitem StrangeCoin, 1250; // Total: 1,500 Strange Coins on first win } specialeffect(FX_FANFARE, SELF, getcharid(3)); if ($@ML_START) dispbottom l("Congratulations! The Moubootaur was defeated in %s.", FuzzyTime($@ML_START)); // Revive you if needed if (Class == Skelli) ReviveHandler(false); sleep2(500); soundeffect("ML/Victory.ogg",0); // Bailout sleep2(rand2(5000, 15000)); warp "001-1", 96, 38; end; OnDoT: if (ispcdead()) DeathHandler(true); percentheal -1, 0; end; OnRev: atcommand("@alive"); atcommand("@refresh"); // If this is your first death EVER, then hold on! if (!@ml_skelli) { @ml_skelli=true; sleep2(50); setpcblock(PCBLOCK_HARD, true); mesc l("STORY MODE ENABLED. Monsters won't attack you, so you can read without worries."), 1; next; soundeffect("ML/Death01.ogg",0); mesn l("Mr. Saves"); mesq l("You have died outside of the world, and as such, the Soul Menhir has no effect. This is a permanent death to you."); next; soundeffect("ML/Death02.ogg",0); mesn l("Mr. Saves"); mesq l("But I'll make an exception for you: If you go back to where you died, you'll revive and can pick where you left off."); next; soundeffect("ML/Death03.ogg",0); mesn l("Mr. Saves"); mesq l("If you logout, then you'll need to overcome my maze and touch an obelisk to return to the world."); next; soundeffect("ML/Death04.ogg",0); mesn l("Mr. Saves"); mesq l("You'll be a weak skeleton on the meanwhile, so be careful. The trial to assess if you're a being worth existing begin now."); mesc l("And now shush, go and try to find your body!"); next; soundeffect("ML/Death05.ogg",0); setpcblock(PCBLOCK_HARD, false); closeclientdialog; } end; // The functions which the Generals call when they die OnBlue: if (!playerattached()) { .@x=72; .@y=41; } else { getmapxy(.@m$, .@x, .@y, 0); } makeitem(MKeyWater, 1, "001-15", .@x+any(-1,0,1), .@y+any(-1,0,1)); .@m=areamonster("001-15", .@x-3, .@y-3, .@x+3, .@y+3, strmobinfo(1, FlyingUnderling), FlyingUnderling, 1); _boostMe(.@m, 3); end; OnBrown: if (!playerattached()) { .@x=25; .@y=55; } else { getmapxy(.@m$, .@x, .@y, 0); } makeitem(MKeyEarth, 1, "001-15", .@x+any(-1,0,1), .@y+any(-1,0,1)); .@m=areamonster("001-15", .@x-3, .@y-3, .@x+3, .@y+3, strmobinfo(1, FlyingUnderling), FlyingUnderling, 1); _boostMe(.@m, 3); end; OnRed: if (!playerattached()) { .@x=74; .@y=55; } else { getmapxy(.@m$, .@x, .@y, 0); } makeitem(MKeyFire, 1, "001-15", .@x+any(-1,0,1), .@y+any(-1,0,1)); .@m=areamonster("001-15", .@x-3, .@y-3, .@x+3, .@y+3, strmobinfo(1, FlyingUnderling), FlyingUnderling, 1); _boostMe(.@m, 3); end; OnGreen: if (!playerattached()) { .@x=42; .@y=36; } else { getmapxy(.@m$, .@x, .@y, 0); } makeitem(MKeyWind, 1, "001-15", .@x+any(-1,0,1), .@y+any(-1,0,1)); .@m=areamonster("001-15", .@x-3, .@y-3, .@x+3, .@y+3, strmobinfo(1, FlyingUnderling), FlyingUnderling, 1); _boostMe(.@m, 3); end; OnWhite: if (!playerattached()) { .@x=25; .@y=25; } else { getmapxy(.@m$, .@x, .@y, 0); } makeitem(MKeySacred, 1, "001-15", .@x+any(-1,0,1), .@y+any(-1,0,1)); .@m=areamonster("001-15", .@x-3, .@y-3, .@x+3, .@y+3, strmobinfo(1, FlyingUnderling), FlyingUnderling, 1); _boostMe(.@m, 3); end; OnGolden: if (!playerattached()) { .@x=40; .@y=50; } else { getmapxy(.@m$, .@x, .@y, 0); } makeitem(MKeyMana, 1, "001-15", .@x+any(-1,0,1), .@y+any(-1,0,1)); .@m=areamonster("001-15", .@x-3, .@y-3, .@x+3, .@y+3, strmobinfo(1, FlyingUnderling), FlyingUnderling, 1); _boostMe(.@m, 3); end; OnPurple: if (!playerattached()) { .@x=70; .@y=30; } else { getmapxy(.@m$, .@x, .@y, 0); } makeitem(MKeyDeath, 1, "001-15", .@x+any(-1,0,1), .@y+any(-1,0,1)); .@m=areamonster("001-15", .@x-3, .@y-3, .@x+3, .@y+3, strmobinfo(1, FlyingUnderling), FlyingUnderling, 1); _boostMe(.@m, 3); end; OnBlack: if (!playerattached()) { .@x=25; .@y=40; } else { getmapxy(.@m$, .@x, .@y, 0); } makeitem(MKeyEvil, 1, "001-15", .@x+any(-1,0,1), .@y+any(-1,0,1)); .@m=areamonster("001-15", .@x-3, .@y-3, .@x+3, .@y+3, strmobinfo(1, FlyingUnderling), FlyingUnderling, 1); _boostMe(.@m, 3); end; // Setup first stage OnFirstStage: // Eliminate all existing keys DelItemFromEveryPlayer(MKeyWater); DelItemFromEveryPlayer(MKeyEarth); DelItemFromEveryPlayer(MKeyFire); DelItemFromEveryPlayer(MKeyWind); DelItemFromEveryPlayer(MKeySacred); DelItemFromEveryPlayer(MKeyMana); DelItemFromEveryPlayer(MKeyDeath); DelItemFromEveryPlayer(MKeyEvil); // Spawn all Generals and their personal guard areamonster("001-15", 62, 37, 76, 45, strmobinfo(1, GunnerUnderling), GunnerUnderling, 2); areamonster("001-15", 62, 37, 76, 45, strmobinfo(1, HalberdUnderling), HalberdUnderling, 1); areamonster("001-15", 62, 37, 76, 45, strmobinfo(1, WizardUnderling), WizardUnderling, 1); areamonster("001-15", 62, 37, 76, 45, strmobinfo(1, BansheeUnderling), BansheeUnderling, 1); .@gen = monster("001-15", 72, 41, "Guardian of Water", TopUnderling, 1, "#Moubootaur::OnBlue"); _boostMe(.@gen, 6); areamonster("001-15", 20, 50, 32, 59, strmobinfo(1, GunnerUnderling), GunnerUnderling, 1); areamonster("001-15", 20, 50, 32, 59, strmobinfo(1, HalberdUnderling), HalberdUnderling, 2); areamonster("001-15", 20, 50, 32, 59, strmobinfo(1, WizardUnderling), WizardUnderling, 1); areamonster("001-15", 20, 50, 32, 59, strmobinfo(1, BansheeUnderling), BansheeUnderling, 1); .@gen = monster("001-15", 25, 55, "Guardian of Earth", TopUnderling, 1, "#Moubootaur::OnBrown"); _boostMe(.@gen, 6); areamonster("001-15", 68, 51, 80, 59, strmobinfo(1, GunnerUnderling), GunnerUnderling, 2); areamonster("001-15", 68, 51, 80, 59, strmobinfo(1, HalberdUnderling), HalberdUnderling, 1); areamonster("001-15", 68, 51, 80, 59, strmobinfo(1, WizardUnderling), WizardUnderling, 1); areamonster("001-15", 68, 51, 80, 59, strmobinfo(1, BansheeUnderling), BansheeUnderling, 1); .@gen = monster("001-15", 74, 55, "Guardian of Fire", TopUnderling, 1, "#Moubootaur::OnRed"); _boostMe(.@gen, 6); areamonster("001-15", 35, 23, 47, 40, strmobinfo(1, GunnerUnderling), GunnerUnderling, 1); areamonster("001-15", 35, 23, 47, 40, strmobinfo(1, HalberdUnderling), HalberdUnderling, 2); areamonster("001-15", 35, 23, 47, 40, strmobinfo(1, WizardUnderling), WizardUnderling, 1); areamonster("001-15", 35, 23, 47, 40, strmobinfo(1, BansheeUnderling), BansheeUnderling, 1); .@gen = monster("001-15", 42, 36, "Guardian of Wind", TopUnderling, 1, "#Moubootaur::OnGreen"); _boostMe(.@gen, 6); areamonster("001-15", 20, 20, 32, 30, strmobinfo(1, GunnerUnderling), GunnerUnderling, 1); areamonster("001-15", 20, 20, 32, 30, strmobinfo(1, HalberdUnderling), HalberdUnderling, 1); areamonster("001-15", 20, 20, 32, 30, strmobinfo(1, WizardUnderling), WizardUnderling, 2); areamonster("001-15", 20, 20, 32, 30, strmobinfo(1, BansheeUnderling), BansheeUnderling, 1); .@gen = monster("001-15", 25, 25, "Guardian of Sacred", TopUnderling, 1, "#Moubootaur::OnWhite"); _boostMe(.@gen, 6); areamonster("001-15", 33, 43, 46, 56, strmobinfo(1, GunnerUnderling), GunnerUnderling, 1); areamonster("001-15", 33, 43, 46, 56, strmobinfo(1, HalberdUnderling), HalberdUnderling, 1); areamonster("001-15", 33, 43, 46, 56, strmobinfo(1, WizardUnderling), WizardUnderling, 1); areamonster("001-15", 33, 43, 46, 56, strmobinfo(1, BansheeUnderling), BansheeUnderling, 2); .@gen = monster("001-15", 40, 50, "Guardian of Mana", TopUnderling, 1, "#Moubootaur::OnGolden"); _boostMe(.@gen, 6); areamonster("001-15", 64, 24, 77, 33, strmobinfo(1, GunnerUnderling), GunnerUnderling, 1); areamonster("001-15", 64, 24, 77, 33, strmobinfo(1, HalberdUnderling), HalberdUnderling, 1); areamonster("001-15", 64, 24, 77, 33, strmobinfo(1, WizardUnderling), WizardUnderling, 1); areamonster("001-15", 64, 24, 77, 33, strmobinfo(1, BansheeUnderling), BansheeUnderling, 2); .@gen = monster("001-15", 70, 30, "Guardian of Death", TopUnderling, 1, "#Moubootaur::OnPurple"); _boostMe(.@gen, 6); areamonster("001-15", 21, 33, 30, 47, strmobinfo(1, GunnerUnderling), GunnerUnderling, 1); areamonster("001-15", 21, 33, 30, 47, strmobinfo(1, HalberdUnderling), HalberdUnderling, 1); areamonster("001-15", 21, 33, 30, 47, strmobinfo(1, WizardUnderling), WizardUnderling, 2); areamonster("001-15", 21, 33, 30, 47, strmobinfo(1, BansheeUnderling), BansheeUnderling, 1); .@gen = monster("001-15", 25, 40, "Guardian of Evil", TopUnderling, 1, "#Moubootaur::OnBlack"); _boostMe(.@gen, 6); // Spawn the map-wide reinforcements, 8 units for 8 generals sleep(100); for (.@i = 0; .@i < 8; .@i++) { areamonster("001-15", 20, 20, 80, 59, strmobinfo(1, FlyingUnderling), FlyingUnderling, 1); sleep(500); } // The Hero warns to be careful soundeffectall("ML/Prologue16.ogg",0,"001-15"); unitwalk(.HERO, 50, 53); unittalk(.HERO, b(col("Watch out!", 1))); // Begin the main loop, or reset it if a bug happens initnpctimer; end; // **MOUBOOTAUR HEARTBEAT** OnTimer25000: OnTimer20000: OnTimer15000: consolewarn("Warning, fail-safe mechanism triggered to Moubootaur (Awakened)."); OnTimer10000: .mana += any(1,2,3,5); // Recover mana // Moubootaur Showdown Effects if (getvariableofnpc(.mapMode, "#Moubootaur") == ML_MAPMODE_DOT) { maptimer2("001-15", 10, "#Moubootaur::OnDoT"); } // Permanent Debuffs checkPenalties(); // TODO: Defeat Conditions // Maybe we need to advance the turn .tbet += 1; if (!(.tbet % 9)) { // Reset turn cycle (90s) and advance turn counter .tbet=0; .turn+=1; // Change map mode if ($@ML_SHOWDOWN >= 3) MapMode(); // Spawn the air corps .@amount = ($@ML_SHOWDOWN >= 2 ? rand2(3,5) : rand2(1,2)); for (.@i=0; .@i < .@amount; .@i++) { .@m=areamonster("001-15", 49, 30, 60, 42, strmobinfo(1, FlyingUnderling), FlyingUnderling, 1); _boostMe(.@m); // Will boost according to showdown state sleep(rand2(100)); // Give a small interval to avoid instakills } // Tally Battlegrounds results if (.battleGrd) { freeloop(true); .bgBonus = 0; .@c=getunits(BL_MOB, .@pcs, false, "001-15", 82, 20, 90, 60); for (.@i = 0; .@i < .@c; .@i++) { .@id = .@pcs[.@i]; if (getunitdata(.@id, UDT_AI) == 1) .bgBonus += getunitdata(.@id, UDT_HP); else .bgBonus -= getunitdata(.@id, UDT_HP); } // Begin new round in Battlegrounds // Enemies and friends are spawned alike for every player present // ...Be they alive, or dead! // _spawnMob(mobId, x1, y1, x2, y2, {name=default, friendly=no}) .@c=getunits(BL_PC, .@pcs, MAX_CYCLE_PC, "001-15"); for (.@i = 0; .@i < .@c; .@i++) { // Spawn allied forces (they're weak) attachrid(.@pcs[.@i]); .@ref = _spawnMob(any(FallenGuard1, FallenGuard2, FallenGuard3), 82, 50, 90, 60, "Allied Guard", true); if (strcharinfo(0) == $MOST_HEROIC$) _spawnMob(DustGatling, 82, 50, 90, 60, "Allied Guard", true); if (getcharid(3) == .lb) _spawnMob(DustRifle, 82, 50, 90, 60, "Allied Guard", true); if (getcharid(3) == .as) _spawnMob(DustRevolver, 82, 50, 90, 60, "Allied Guard", true); if (islegendary()) _boostMe(.@ref, 0); // Consume a town preparation unit and spawn a strong~ish guard. if ($ML_PREPARADNESS) { $ML_PREPARADNESS -= 1; .@ref = _spawnMob(any(FallenGuard1, FallenGuard2), 82, 50, 90, 60, "Allied Guard", true); _boostMe(.@ref, limit(0, log2($ML_PREPARADNESS), 22)); } detachrid(); // Spawn enemy forces (they're strong) for (.@i=0; .@i <= rand2(3); .@i++) { .@mob = _spawnMob(any(Moubi, BloodyMouboo, Shrewboo, AlphaMouboo, Mouboo, Shrewboo, Mouboo, BloodyMouboo), 82, 20, 90, 26); _boostMe(.@mob); unitwalk(.@mob, 85, 52); } } // Announce the new turn begin if (.bgBonus > 0) { .@msg$ = ", the battle is in ##BTalpans##b favor!"; soundeffectall("ML/TurnGood.ogg",0,"001-15"); } else if (.bgBonus < 0) { .@msg$ = ", the battle is in ##BMonsters##b favor!"; soundeffectall("ML/TurnBad.ogg",0,"001-15"); } mapannounce("001-15", sprintf("Turn %d%s", .turn, .@msg$), 0); freeloop(false); } // .battleGrd = if Eastern BattleGround is active } // If the Moubootaur cannot attack (e.g. during cutscenes), just continue if (!.canAttack) { initnpctimer; end; } // TODO: Maybe slide the Moubootaur a couple tiles, or set this as a skill? // (So melee players have more difficulty tracking it) // Otherwise, we handle the Moubootaur's skill array // Maybe he's recovering mana, though, in which case nothing will happen. if (rand2(90) > .mana) { initnpctimer; end; } // Mana cost for skill use. It can go negative, all skills use same MP amount .mana -= 5; // Give players 500ms to prepare specialeffect(FX_SPECIAL, AREA, .ML); sleep(500); // Determine how many additional attacks the Moubootaur will execute .@bonusAttacks = 1; // Retrieve Moubootaur's current location getmapxy(.@m$, .@x, .@y, UNITTYPE_MOB, .ML); // Determine some of the possible targets. // variable ; tracker .@mvp = 0; // An array of (char, score) for relative_arr_rnd .@rnd = 0; // Random selection .@tkn = 0; .@def = 1; // The Tanker (most DEF) .@dps = 0; .@atk = 1; // The DPS (most ATK) .@mag = 0; .@mtk = 1; // The Mage (most MATK) .@c=getunits(BL_PC, .@pcs, MAX_CYCLE_PC, .@m$); for (.@i = 0; .@i < .@c; .@i++) { // Get the account ID from the array .@id = .@pcs[.@i]; // Dead players do not count if (readparam(Class, .@id) == Skelli || readparam(Hp, .@id) < 1) continue; // TODO: Do not target players out of AoE magic's reach? (for regen) // Determine the random target // any_of(.@pcs) would be ideal, but we cannot ensure liveness :< if (!.@rnd || !rand2(.@c)) .@rnd=.@id; // Definition: Tanker .@tmp = readbattleparam(.@id, UDT_DEF) / 3; // 3 def = 1 point .@tmp += readbattleparam(.@id, UDT_HP) / 1000; // 1kHP = 1 point FIXME .@tmp += readbattleparam(.@id, UDT_MDEF) / 10; // 10 mdef = 1 point .@tmp += readbattleparam(.@id, UDT_FLEE) / 10; // 10 evade = 1 point if (.@tmp > .@def) { .@tkn=.@id; .@def=.@tmp; } // Definition: DPS // Every two tiles of attack increases average damage in 10% // Then it is converted to damage per second // And add crit chance and hit chance as some fake additional DPS .@tmp = (readbattleparam(.@id, UDT_ATKMIN) + readbattleparam(.@id, UDT_ATKMAX)) / 2; .@tiles = 10 + (readbattleparam(.@id, UDT_ATKRANGE) / 2); .@tmp = .@tmp * .@tiles / 10; .@tmp = (.@tmp/2) * 1000 / readbattleparam(.@id, UDT_ADELAY); .@tmp += readbattleparam(.@id, UDT_CRIT) * 5; .@tmp += readbattleparam(.@id, UDT_HIT) * 4; if (.@tmp > .@atk) { .@dps=.@id; .@atk=.@tmp; } // Definition: The Wizard .@tmp = (readbattleparam(.@id, UDT_MATKMIN) + readbattleparam(.@id, UDT_MATKMAX)) / 2; .@tmp += readbattleparam(.@id, UDT_CRIT) * 2; // 4 crit = 1 point .@tmp += readbattleparam(.@id, UDT_HIT) * 2; // 4 acc. = 1 point if (.@tmp > .@mtk) { .@mag=.@id; .@mtk=.@tmp; } // Definition: MVP .@tmp = 0; .@tmp = readbattleparam(.@id, UDT_DEF) / 3; // 3 def = 1 point .@tmp += readbattleparam(.@id, UDT_HP) / 1000; // 1kHP = 1 point .@tmp += readbattleparam(.@id, UDT_MDEF) / 10; // 10 mdef = 1 point .@tmp += readbattleparam(.@id, UDT_FLEE) / 10; // 10 evade = 1 point .@tmp += readbattleparam(.@id, UDT_ATKRANGE) * 3; // 1 tile = 3 points .@tmp += (readbattleparam(.@id, UDT_ATKMIN) + readbattleparam(.@id, UDT_ATKMAX)) / 20; // 10 atk = 1 point .@tmp += max(2000-readbattleparam(.@id, UDT_ADELAY), 0) / 50; // 1 point for every 50ms not added to attack speed .@tmp += readbattleparam(.@id, UDT_PDODGE); // 1 block = 1 point FIXME .@tmp += readbattleparam(.@id, UDT_CRIT) / 4; // 4 crit = 1 point .@tmp += readbattleparam(.@id, UDT_HIT) / 4; // 4 acc. = 1 point // Push to the array array_push(.@mvp, .@id); array_push(.@mvp, .@tmp); } // Tally .@mvp where more points = more likely choice .@mvp = relative_array_random(.@mvp); // Determine how much HP (in %) the Moubootaur has left .@hpc = getunitdata(.ML, UDT_HP) * 100 / getunitdata(.ML, UDT_MAXHP); // Determine how much HP (in %) was lost since last skill usage .@lost = .lastHpc - .@hpc; // "If the Moubootaur is failing too fast, intervene in his favor" -- Mr. Saves if (.@lost > 3) { soundeffectall("ML/Sublima.ogg",0,"001-15"); // Who said the Moubootaur can't use healing items? // TODO: Move this to a timer instead and heal gradually, 0.1% per sec? specialeffect(FX_HEALINGPART, AREA, .ML); .@hp=getunitdata(.ML, UDT_HP); .@mp=getunitdata(.ML, UDT_MAXHP); .@hp = limit(.@hp, .@hp + (.@mp / 100), .@mp); // Regenerates 1% HP setunitdata(.ML, UDT_HP, min(.@hp, .@mp)); sleep(250); } if (.@lost > 2) .@bonusAttacks += .@lost - 2; if (.@lost > 1) .mana += .@lost; // Update the memory of HP lost since last skill use .lastHpc = .@hpc; // Determine if plataform will be changed (and locked!) // Recover HP if it somehow went down faster than scheduled on platform hop // Also, event triggers to change the stage based on damage sustained // (And ensure Moubootaur does not "return" to previous platforms if he regen) // XXX: Moubootaur goes from [1] to [8] in $@MLFX. .@presumed_index = abs(12 - (.@hpc / 12)); // The Moubootaur has changed platform, and shall heal "fully" if (.MLindex < .@presumed_index && $@ML_SHOWDOWN < 6) { .MLindex += 1; .@chunks = 100 - (.MLindex * 12); .@presumed_health = abs(getunitdata(.ML, UDT_MAXHP) * .@chunks / 100); setunitdata(.ML, UDT_HP, .@presumed_health); if (.MLindex != 1) { soundeffectall("ML/PlatChange.ogg",0,"001-15"); unittalk(.ML, "Come catch me... if you can, that is! HAHAHAH!"); } unitwarp(.ML, "001-15", $@MLFX[.MLindex], $@MLFY[.MLindex]); .warpsOnline = false; // Maybe this is the first time ever the Moubootaur changes platforms // Or maybe we just need to move the Ars Statue. Keep in mind .@x/.@y // still reflect the situation *before* the Moubootaur jumped platforms if (.MLindex == 1) goto OnThirdStage; else unitwarp(getnpcid("Ars Statue"), "001-15", .@x, .@y); } // FIXME: Maybe this should not be inside the skill loop? if (.@hpc < 90 && debug) { // goto OnThirdStage; kamibroadcast("The demo has ended."); sleep(100); unitkill(.ML); sleep(1000); killmonsterall("001-15"); stopnpctimer; end; } // Spawn a fiend, always, unconditional and for free .@m=monster(.@m$, .@x, .@y, "Mouboo", ($@ML_SHOWDOWN == 6 ? Shadowboo : Moubi), 1); setunitdata(.@m, UDT_RACE, RC_Legendary); set_aggro(.@m); _boostMe(.@m); // If there are no alive players nearby, (i.e. any determination is 0) // then use a fixed, strong healing skill instead of a random skill if (!.@mvp || !.@rnd || !.@tkn || !.@dps || !.@mag) { soundeffectall("ML/Sublima.ogg",0,"001-15"); unittalk(.ML, "Sublima Regeratio!"); .mana += rand2(6); // Moubootaur may recover some mana! .@hp=getunitdata(.ML, UDT_HP); .@mp=getunitdata(.ML, UDT_MAXHP); .@hp = limit(.@hp, .@hp + (.@mp / 20), .@mp); // Regenerates 5% HP setunitdata(.ML, UDT_HP, min(.@hp, .@mp)); initnpctimer; end; } // If the warps are not online, he'll recover a tiny bit HP (silent, bonus) if (!.warpsOnline) { .@hp=getunitdata(.ML, UDT_HP); .@mp=getunitdata(.ML, UDT_MAXHP); .@hp = limit(.@hp, .@hp + rand2(500,750), .@mp); // Regenerates some HP setunitdata(.ML, UDT_HP, min(.@hp, .@mp)); } else { .@hp=getunitdata(.ML, UDT_HP); .@mp=getunitdata(.ML, UDT_MAXHP); .@hp = limit(.@hp, .@hp + rand2(100,250), .@mp); // Like players do! setunitdata(.ML, UDT_HP, min(.@hp, .@mp)); } // Determine the skill, more likely to use lower numbers at low HP // Skills ranked higher than 12 don't show up when HP is exausthed. // (Note "default" can show up always) .@skill = (rand2(25 * .@hpc / 100) + rand2(25)) / 2; // By arranging the skill IDs smartly, so stronger skills are more likely on // less HP and vice-versa (weaker skills are less likely based on damage) .@mob = .ML; .@msg$ = ""; .@lv = 120; .@t = 2+rand2(8)+rand2(15); .@target = any(.@mvp, .@dps, .@tkn); .@sub = any(.@tkn, .@mag, .@rnd); // TODO: Use the actual skill // Also, debuffs should last longer and hit area + any of the special targets // FIXME: Mob IDs, coordinates // specialeffect(ALL_SAMEMAP ALL_CLIENT AREA CHAT PARTY GUILD SELF) soundeffectall(sprintf("ML/Skill%02d.ogg", .@skill),0,"001-15"); switch (.@skill) { case 1: //specialeffect(5000, ALL_SAMEMAP, "#Moubootaur"); // Option A (ManaPlus) //soundeffectall("Skill01.ogg",0,"001-15"); // Option B (ManaVerse) .@msg$ = sprintf("Witness my sublime rain of death. Regeneration!"); .@hp=getunitdata(.@mob, UDT_HP); .@mp=getunitdata(.@mob, UDT_MAXHP); .@hp = limit(.@hp, .@hp + (.@mp / 25), .@mp); // Regenerates 4% HP setunitdata(.@mob, UDT_HP, min(.@hp, .@mp)); .@mobid=any(GunnerUnderling, WizardUnderling, BansheeUnderling); monster(.@m$, .@x, .@y, strmobinfo(1, .@mobid), .@mobid, 1); if ($@ML_SHOWDOWN != 6) monster(.@m$, .@x, .@y, strmobinfo(1, DeathCat), DeathCat, 1); else monster(.@m$, .@x, .@y, strmobinfo(1, Shadowboo), Shadowboo, 1); break; case 2: mapannounce("001-15", "Moubootaur : ##BAncient Magic: Silencing Nuke", 0); squareharm(.@mob, 14, rand2(450, 750), HARM_MISC, Ele_Dark); areasc2("001-15", 40, 35, 20, 15000, SC_SILENCE, BL_PC, 1); break; case 3: .@msg$ = sprintf("%s, I'll show you no mercy! ##BThunder Bolt##b!", strcharinfo(0, "cursed player", .@target)); .@PW=125; .@SPW=25; .@RG=1; .@mtk = calcdmg(.@mob, .@target, HARM_MAGI); .@dmg = .@mtk * .@PW / 100; .@dsb = .@mtk * .@SPW / 100; sleep(1000); specialeffect(FX_LIGHTNING, AREA, .@target); squareharm(.@target, .@RG, .@dsb, HARM_MAGI, Ele_Wind, .@mob); harm(.@target, .@dmg, HARM_MAGI, Ele_Holy, .@mob); break; case 4: .@msg$ = sprintf("##BAncient Magic: Obliterate"); squareharm(.@mob, 7, rand(12000, 18000), HARM_MAGI, Ele_Dark); harm(.@target, rand(1000, 4000), HARM_MAGI, Ele_Holy, .@mob); break; case 5: .@msg$ = sprintf("I shall ##Bdisable##b you, %s!", strcharinfo(0, "cursed player", .@target)); specialeffect(FX_SMOKE, AREA, .ML); attachrid(.@target); SC_Bonus(.@t, SC_SILENCE, 1); SC_Bonus(.@t, SC_BLIND, 1); SC_Bonus(.@t/3, SC_CURSE, 1); detachrid(); break; case 6: .@msg$ = sprintf("This battle is over, %s! ##BThunder Needle##b!", strcharinfo(0, "cursed player", .@target)); .@PW=35; .@SPW=5; .@RG=2; .@mtk = calcdmg(.@mob, .@target, HARM_MAGI); .@dmg = .@mtk * .@PW / 100; .@dsb = .@mtk * .@SPW / 100; sleep(1000); specialeffect(FX_LIGHTNING, AREA, .@target); squareharm(.@target, .@RG, .@dsb, HARM_MAGI, Ele_Wind, .@mob); harm(.@target, .@dmg, HARM_MAGI, Ele_Holy, .@mob); break; case 7: .@msg$ = sprintf("##BMagic: Armageddon"); squareharm(.@mob, 6, rand2(8300, 9500), HARM_MAGI, Ele_Fire); break; case 8: .@msg$ = sprintf("##BMagic: Gaia Break"); sc_start SC_INCDEFRATE, 15000, 15, .@mob; rectharm(.@mob, 2, 5, rand2(8250, 9350), HARM_MAGI, Ele_Earth); break; case 9: .@msg$ = sprintf("##BMagic: Ground Strike"); .@EF=any(SC_STUN, SC_BLIND, SC_BLOODING, SC_BLIND, SC_BLOODING); areasc2("001-15", .@x, .@y, 3, 12000, .@EF, BL_PC, 1); squareharm(.@mob, 3, rand2(9250, 10350), HARM_PHYS, Ele_Neutral); break; case 10: .mana += 1; .@msg$ = sprintf("##BMagic: Tempest"); squareharm(.@mob, 3, rand2(8200, 9300), HARM_MAGI, Ele_Wind); break; case 11: .@msg$ = sprintf("Chaos shall be my founding stone! Falling star!"); attachrid(.@rnd); percentheal -15, -25; detachrid(); .@mobid=any(HalberdUnderling, FlyingUnderling, TopUnderling, Shadowboo); monster(.@m$, .@x, .@y, strmobinfo(1, .@mobid), .@mobid, 1); break; case 12: .@msg$ = sprintf("I demand this world! Tyranny!"); attachrid(.@rnd); percentheal -5, -5; SC_Bonus(.@t, any(SC_BLIND, SC_POISON), 1); detachrid(); .@mobid=any(HalberdUnderling, FlyingUnderling, TopUnderling, Shadowboo); monster(.@m$, .@x, .@y, strmobinfo(1, .@mobid), .@mobid, 1); break; case 13: .@msg$ = sprintf("Stop on your tracks, unfair being! Freeze!"); attachrid(.@rnd); SC_Bonus((.@t * 2 / 3), any(SC_FREEZE, SC_SLEEP, SC_SLEEP, SC_SLEEP), 1); detachrid(); .@mobid=any(HalberdUnderling, WizardUnderling); monster(.@m$, .@x, .@y, strmobinfo(1, .@mobid), .@mobid, 1); break; case 14: .@msg$ = sprintf("Tremble before my awesome power!"); .@mx=any(80, 80, 90); .@my=60; .@x=20; .@y=20; .@x1=rand2(.@mx, .@x); .@x2=rand2(.@mx, .@x); .@x3=rand2(.@mx, .@x); .@y1=rand2(.@my, .@y); .@y2=rand2(.@my, .@y); .@y3=rand2(.@my, .@y); .@x4=rand2(.@mx, .@x); .@x5=rand2(.@mx, .@x); .@x6=rand2(.@mx, .@x); .@y4=rand2(.@my, .@y); .@y5=rand2(.@my, .@y); .@y6=rand2(.@my, .@y); // TODO: Maybe replace Dummy/EnergyBall with an invisible monster/FX? .@mobId = any(EntAbomination, Dummy, Pumpkandy); .@t1=monster("001-15", .@x1, .@y1, "", .@mobId, 1); .@t2=monster("001-15", .@x2, .@y2, "", .@mobId, 1); .@t3=monster("001-15", .@x3, .@y3, "", .@mobId, 1); .@t4=monster("001-15", .@x4, .@y4, "", .@mobId, 1); .@t5=monster("001-15", .@x5, .@y5, "", .@mobId, 1); .@t6=monster("001-15", .@x6, .@y6, "", .@mobId, 1); specialeffect(67, AREA, .@t1); specialeffect(67, AREA, .@t2); specialeffect(67, AREA, .@t3); specialeffect(67, AREA, .@t4); specialeffect(67, AREA, .@t5); specialeffect(67, AREA, .@t6); immortal(.@t1); immortal(.@t2); immortal(.@t3); immortal(.@t4); immortal(.@t5); immortal(.@t6); sleep(1500); // Saw a pillar? RUN! specialeffect(FX_LIGHTNING, AREA, .@t1); specialeffect(FX_LIGHTNING, AREA, .@t2); specialeffect(FX_LIGHTNING, AREA, .@t3); specialeffect(FX_LIGHTNING, AREA, .@t4); specialeffect(FX_LIGHTNING, AREA, .@t5); specialeffect(FX_LIGHTNING, AREA, .@t6); sleep(500); specialeffect(FX_CRITICAL, AREA, .@t1); specialeffect(FX_CRITICAL, AREA, .@t2); specialeffect(FX_CRITICAL, AREA, .@t3); specialeffect(FX_CRITICAL, AREA, .@t4); specialeffect(FX_CRITICAL, AREA, .@t5); specialeffect(FX_CRITICAL, AREA, .@t6); squareharm(.@t1, 3, 14500, HARM_MISC, Ele_Neutral); squareharm(.@t2, 3, 14500, HARM_MISC, Ele_Neutral); squareharm(.@t3, 3, 14500, HARM_MISC, Ele_Neutral); squareharm(.@t4, 3, 14500, HARM_MISC, Ele_Neutral); squareharm(.@t5, 3, 14500, HARM_MISC, Ele_Neutral); sleep(1000); // FIXME: M+ fails to remove them, need @refresh (maybe @refreshall?) // NOTE: The effect NÂș 67 (halo circle) is also not cleaned by M+ unitkill(.@t1); unitkill(.@t2); unitkill(.@t3); unitkill(.@t4); unitkill(.@t5); unitkill(.@t6); //sleep(150); //atcommand("@refreshall"); break; case 15: .@msg$ = sprintf("And then... There was a quake. And all life died. Bleed!"); attachrid(.@target); SC_Bonus(.@t, SC_BLOODING, 1); detachrid(); .@mobid=any(HalberdUnderling, BansheeUnderling); monster(.@m$, .@x, .@y, strmobinfo(1, .@mobid), .@mobid, 1); .@bonusAttacks += 1; break; case 16: .@msg$ = sprintf("Puny mortal, do your best to entertain me! Curse!"); attachrid(.@target); SC_Bonus(.@t, SC_CURSE, 1); detachrid(); .@mobid=any(WizardUnderling, BansheeUnderling); monster(.@m$, .@x, .@y, strmobinfo(1, .@mobid), .@mobid, 1); break; case 17: .@msg$ = sprintf("The problem with typos is - unpredictable side effects."); attachrid(.@target); SC_Bonus(.@t, any(SC_SILENCE, SC_CURSE, SC_FREEZE, SC_BLOODING, SC_BLIND, SC_POISON, SC_DPOISON, SC_POISON, SC_BURNING, SC_SLEEP), 1); detachrid(); .@mobid=any(WizardUnderling, BansheeUnderling, TopUnderling, GunnerUnderling, FlyingUnderling, HalberdUnderling, Shadowboo); monster(.@m$, .@x, .@y, strmobinfo(1, .@mobid), .@mobid, 1); break; case 18: mapannounce("001-15", "Moubootaur : ##BAncient Magic: Disarm Homun", 0); areasc2("001-15", .@x, .@y, 20, 30000, SC_STUN, BL_HOM, 1); break; case 19: mapannounce("001-15", "Moubootaur : ##BAncient Magic: Sleep", 0); areasc2("001-15", .@x, .@y, 20, 15000, SC_SLEEP, BL_PC, 1); break; case 20: mapannounce("001-15", "Moubootaur : ##BAncient Magic: Burning", 0); areasc2("001-15", .@x, .@y, 20, 15000, SC_BURNING, BL_PC, 1); break; case 21: mapannounce("001-15", "Moubootaur : ##BAncient Magic: Fear", 0); areasc2("001-15", .@x, .@y, 20, 15000, SC_FEAR, BL_PC, 1); break; case 22: .@msg$ = sprintf("##BAncient Magic: Terminate Homunculi"); squareharm(.@mob, 14, rand2(3000, 5000), HARM_MISC, Ele_Dark, .@mob, BL_MER | BL_HOM); specialeffect(FX_LIGHTNING, AREA, .@sub); harm(.@sub, rand2(1000, 2500), HARM_MAGI, Ele_Holy, .@mob); break; case 23: .@msg$ = sprintf("There is no free speech. Censorship!"); attachrid(.@target); SC_Bonus(.@t, SC_SILENCE, 1); detachrid(); .@mobid=any(BansheeUnderling, GunnerUnderling); monster(.@m$, .@x, .@y, strmobinfo(1, .@mobid), .@mobid, 1); break; case 24: mapannounce("001-15", "Moubootaur : ##BAncient Magic: Lag", 0); areasc2("001-15", .@x, .@y, 20, 15000, SC_CONFUSION, BL_PC, 1); // TODO: Run refreshall so everyone loses targetting, and slide .ML? //sleep(150); //atcommand("@refreshall"); break; case 25: mapannounce("001-15", "Moubootaur : ##BAncient Magic: Wizcat", 0); for (.@i=0;.@i <= 8+rand2(7);.@i++) { .@m=monster("001-15", rand2(30,50), rand2(30,45), "Reinforcement", BlackCat, 1); setunitdata(.@m, UDT_RACE, RC_Legendary); } if ($@ML_SHOWDOWN == 6) monster(.@m$, .@x, .@y, strmobinfo(1, Shadowboo), Shadowboo, 1); break; default: .@msg$ = sprintf("Come forth, my minions! Wreak chaos and havoc!"); specialeffect(FX_MGWARP, AREA, .ML); // Maybe 65 would also work sleep(1000); // Default amount: Half players + up to 5 .@max = (getmapusers("001-15")/2) + rand2(5); // Keep boundaries: Never less than 2, never more than 12 .@max = limit(2, .@max, 12); for (.@i=0; .@i < .@max; .@i++) { .@mid = any(WizardUnderling, BansheeUnderling, TopUnderling, GunnerUnderling, FlyingUnderling, HalberdUnderling, Tortuga, SacredWisp, EvilWisp, PanthomWisp, EpiphanyWisp, WindElement, EarthElement, WaterElement, FireElement); .@x = monster(.@m$, .@x, .@y, strmobinfo(1, .@mid), .@mid, 1); set_aggro(.@x); } // Additional Shadowboo during final stage if ($@ML_SHOWDOWN == 6) monster(.@m$, .@x, .@y, strmobinfo(1, Shadowboo), Shadowboo, 1); break; } if (.@msg$ != "") unittalk(.@mob, .@msg$); // The Moubootaur also hits two targets for (.@i=0; .@i < .@bonusAttacks; .@i++) { // Main skill goes against target1, which is 75% MVP 25% Tanker .@target1 = any(.@mvp, .@tkn, .@mvp, .@mvp); // Secondary is 50% tanker, 33% DPS and 17% mage .@target2 = any(.@dps, .@mag, .@tkn, .@dps, .@tkn, .@tkn); specialeffect(FX_LIGHTNING, AREA, .@target1); specialeffect(FX_CRITICAL, AREA, .@target1); specialeffect(FX_LIGHTNING, AREA, .@target2); harm(.@target1, rand2(2250, 3500), HARM_MAGI, Ele_Dark, .ML); harm(.@target2, rand2(1250, 2400), HARM_MAGI, Ele_Dark, .ML); // If there will be another combo, give 250 ms between each if ((.@i + 1) < .@bonusAttacks) sleep(250); } // Loop forever initnpctimer; end; // Scope: MLDIE_ // DeathHandler( end = false , dead = true ) public function DeathHandler { // First death, handle it! if (Class != Skelli) { MLDIE_Class = Class; jobchange Skelli; } // Save your equipment selection for a quick equip() after unstoring // (Handled by ReviveHandler) for ( .@i=EQI_HEAD_TOP ; .@i<=EQI_SHADOW_ACC_L ; .@i++ ) { array_push( MLDIE_Eqp, getequipid(.@i) ); } // Eliminate any key you may have and revive the trupe // No state check is necessary; the keys should not be duplicated if (countitem(MKeyWater)) { delitem MKeyWater, 1; .@gen = monster("001-15", 72, 41, "Guardian of Water", TopUnderling, 1, "#Moubootaur::OnBlue"); _boostMe(.@gen, 12); } if (countitem(MKeyEarth)) { delitem MKeyEarth, 1; .@gen = monster("001-15", 25, 55, "Guardian of Earth", TopUnderling, 1, "#Moubootaur::OnBrown"); _boostMe(.@gen, 12); } if (countitem(MKeyFire)) { delitem MKeyFire, 1; .@gen = monster("001-15", 74, 55, "Guardian of Fire", TopUnderling, 1, "#Moubootaur::OnRed"); _boostMe(.@gen, 12); } if (countitem(MKeyWind)) { delitem MKeyWind, 1; .@gen = monster("001-15", 42, 36, "Guardian of Wind", TopUnderling, 1, "#Moubootaur::OnGreen"); _boostMe(.@gen, 12); } if (countitem(MKeySacred)) { delitem MKeySacred, 1; .@gen = monster("001-15", 25, 25, "Guardian of Sacred", TopUnderling, 1, "#Moubootaur::OnWhite"); _boostMe(.@gen, 12); } if (countitem(MKeyMana)) { delitem MKeyMana, 1; .@gen = monster("001-15", 40, 50, "Guardian of Mana", TopUnderling, 1, "#Moubootaur::OnGolden"); _boostMe(.@gen, 12); } if (countitem(MKeyDeath)) { delitem MKeyDeath, 1; .@gen = monster("001-15", 70, 30, "Guardian of Death", TopUnderling, 1, "#Moubootaur::OnPurple"); _boostMe(.@gen, 12); } if (countitem(MKeyEvil)) { delitem MKeyEvil, 1; .@gen = monster("001-15", 25, 40, "Guardian of Evil", TopUnderling, 1, "#Moubootaur::OnBlack"); _boostMe(.@gen, 12); } // All your items go away to Special Storage charcommand("@storeall 6"); // All your money goes away to Special Storage MLDIE_Zeny += Zeny; Zeny = 0; // Permanent Debuff is handled by hub functions // TODO FIXME: Sometimes the penalty loop through? D: [CRITICAL] // Death NPC which recovers all your items // Can only appear if you're in 001-15, obviously getmapxy(.@m$, .@x, .@y, 0); if (.@m$ == "001-15") { // Player_Name#ML!GIDGID // So 13 chars available for char name if (getstrlen(strcharinfo(0)) > 12) .@n$=substr(strcharinfo(0), 0, 12); else .@n$=strcharinfo(0); .@n$=sprintf("%s#ML!%06d", .@n$, getcharid(0)); .@bodyId = getnpcid(.@n$); debugmes "%s is ID %d [DeathHandler]", .@n$, .@bodyId; // No body found, so create one // You can recover your belongings, but you have only 15 minutes! if (.@bodyId < 1) { if (array_find($@ML_DUPES, getcharid(0)) < 0) { npc_duplicate("Dead_Body#ML!000000", .@n$, .@m$, .@x, .@y, (Sex ? NPC_GUARD_DEAD : NPC_INJURIED_GIRL), 0, 0, 0); // Tell the duplicate control and our internal control htput($@_DUPES, .@n$, gettimetick(2) + 900); array_push($@ML_DUPES, getcharid(0)); } } } // Warp you to the respawn area if set (and revives you) // On the first death, it also explains to you what happened if (getarg(1, true)) { addtimer(20, "#Moubootaur::OnRev"); warp "001-15", rand2(84, 87), rand2(21, 26); } // We were instructed to terminate the interaction if (getarg(0, false)) end; // Otherwise, return the control to caller return; } // Undo everything DeathHandler did, and takes same arguments public function ReviveHandler { // Clear Elli's Ban because otherwise, it's eternal sc_end SC_ELLIBAN; // If you are NOT dead, then RAISE AN ERROR! // Ignore termination instruction, leave dialogue hanging // (However, OVER9000 handler could result in that?) if (Class != Skelli) { Exception(sprintf("Cannot revive player \"%s\" who is not dead!", strcharinfo(0)), RB_PLEASEREPORT|RB_DEBUGMES|RB_SPEECH|RB_IRCBROADCAST); getmapxy(.@m$, .@x, .@y, 0); consolebug("%s (%d, %d)", .@m$, .@x, .@y); return; } // Restore your Class jobchange MLDIE_Class; Zeny += MLDIE_Zeny; // Reset these variables MLDIE_Class = 0; MLDIE_Zeny = 0; // !! Unstash all your items !! unstoreall(6); /* This code does not work sc_start SC_INVINCIBLE, 99999, 1, 10000, SCFLAG_NOAVOID|SCFLAG_FIXEDTICK|SCFLAG_NOICON; openstorage(6, STORAGE_ACCESS_GET); sc_end SC_INVINCIBLE; sc_start SC_INVINCIBLEOFF, 1, 1; */ // Re-equip your items // Do not call equip() if < 1, it can be negative after all for (.@i=0 ; .@i < getarraysize(MLDIE_Eqp) ; .@i++) { if (MLDIE_Eqp[.@i] > 1) equip(MLDIE_Eqp[.@i]); } deletearray MLDIE_Eqp; // Eliminate DeathNPC if it exists array_remove($@ML_DUPES, getcharid(0)); // Player_Name#ML!GIDGID // So 13 chars available for char name if (getstrlen(strcharinfo(0)) > 12) .@n$=substr(strcharinfo(0), 0, 12); else .@n$=strcharinfo(0); .@n$=sprintf("%s#ML!%06d", .@n$, getcharid(0)); .@t = htget($@_DUPES, .name$, 0); if (.@t) { .@t = min(gettimetick(2)+2, .@t); htput($@_DUPES, .name$, .@t); } // Reapply any penalties if pertinent if (getmap() == "001-15") _calcDebuff(); // We were instructed to terminate the interaction if (getarg(0, false)) end; // Otherwise, return the control to caller return; } // Begin Introduction Cutscene (Event Start!!) OnIntroCutscene: // Reset Showdown Status (Clear Previous Attempts) stopnpctimer; $@ML_SHOWDOWN=1; .turn = 0; .tbet = 0; .ML = 0; disablenpc "#ML_EastWarp"; disablenpc "#MLWA+"; .warpsOnline = true; .canAttack = false; .battleGrd = false; .mapMode = MapMode(false); .lastHpc = 100; .bgBonus = 0; .MLindex = 0; deletearray $@ML_DUPES; $@ML_START=gettimetick(2); donpcevent "Ars Statue::OnReset"; // Create Andrei Sakar .HERO=getnpcid("Andrei"); unitwarp(.HERO, "001-15", 55, 47); //.HERO=monster("001-15", 55, 47, "Andrei Sakar", Skeleton, 1); //setunitdata(.HERO, UDT_AI, 1); // 1 = AI_ATTACK = attack/friendly (FIXME) //setunitdata(.HERO, UDT_MODE, MD_CANMOVE|MD_PLANT|MD_BOSS|MD_NOKNOCKBACK); //immortal(.HERO); //sc_start SC_STUN, 99990, 1, 10000, SCFLAG_NOAVOID|SCFLAG_FIXEDTICK, .HERO; // This is the prologue cutscene. It's kinda long so everyone has time to arrive. sleep(1000); soundeffectall("ML/Tiki01.ogg",0,"001-15"); unittalk(getnpcid("Tiki"), "Hey, talk to me if you need to acquire anything!"); sleep(5000); soundeffectall("ML/Prologue01.ogg",0,"001-15"); unittalk(.HERO, "Oooohhhh. Someone actually came!"); sleep(5000); soundeffectall("ML/Prologue02.ogg",0,"001-15"); unittalk(.HERO, "Maybe I should wait a while to see if more people show up!"); sleep(5000); soundeffectall("ML/Tiki02.ogg",0,"001-15"); unittalk(getnpcid("Tiki"), "Hey, don't I count?! I can sell information and more!"); sleep(5000); soundeffectall("ML/Prologue03.ogg",0,"001-15"); unittalk(.HERO, "Sorry, I haven't existed properly for a while."); sleep(5000); soundeffectall("ML/Prologue04.ogg",0,"001-15"); unittalk(.HERO, "But this is the Mana Plane section of the World Heart, so it matters not."); sleep(8000); soundeffectall("ML/Prologue05.ogg",0,"001-15"); unittalk(.HERO, "Anyway, my name is Andrei, and I used to be a legendary hero of this world."); sleep(8000); soundeffectall("ML/Prologue06.ogg",0,"001-15"); unittalk(.HERO, "Then, during a campaign to the Fortress, I noticed the lack of aid from Artis region."); sleep(12000); soundeffectall("ML/Prologue07.ogg",0,"001-15"); unittalk(.HERO, "While investigating Artis, Isbamuth minions killed me as a sacrifice to revive the Moubootaur."); sleep(9000); soundeffectall("ML/Prologue08.ogg",0,"001-15"); unittalk(.HERO, "I'm not entirely sure why. Mr. Saves should have prevented Talpans from allying with the Moubootaur and monsters from allying with Elli, but I assume exceptions were made. Likely a bug!"); sleep(16000); soundeffectall("ML/Prologue09.ogg",0,"001-15"); unittalk(.HERO, "Anyway, this means I'm unable to use the Soul Menhir and became a lost soul. Same will happen to all of you, shall you die here!"); sleep(9000); soundeffectall("ML/Prologue10.ogg",0,"001-15"); unittalk(.HERO, b("But if you find your corpse back, or one of the Obelisks of Power, you might be able to return.")); sleep(8000); // Official justification in case your body (Death NPC) is not available. soundeffectall("ML/Prologue11.ogg",0,"001-15"); unittalk(.HERO, "Even if your body is consumed like mine was, the Obelisk of Power can still bring you back. However..."); sleep(8000); soundeffectall("ML/Prologue12.ogg",0,"001-15"); unittalk(.HERO, "Elli still has jurisdiction over Talpans. She probably vetoed my return, because I couldn't revive! Hahahah."); /* Check Elli's curse on all players */ sleep(7000); checkPenalties(22); npcstand "Andrei"; sleep(3500); soundeffectall("ML/Prologue13.ogg",0,"001-15"); unittalk(.HERO, "Anyway! The Moubootaur is in the area ahead plotting something, but you'll need to find a way to enable the warp I'm standing on first."); sleep(10500); soundeffectall("ML/Prologue14.ogg",0,"001-15"); unittalk(.HERO, "I'll enable the warp to the west so you can explore! Good luck, adventurers!"); sleep(4500); enablenpc "#MLWA+"; sleep(250); soundeffectall("ML/Prologue15.ogg",0,"001-15"); unittalk(.HERO, "Also, be careful! The warps work differently for everyone, and they can even point to themselves!"); unitwalk(.HERO, 55, 53); // Maybe npcwalkto - nah, no NPC arg sleep(250); // And now that everything is ready and done, start the timer and set turn 1 .tbet = 0; .turn = 1; //sc_end SC_STUN, .HERO; sleep(2500); goto OnFirstStage; // Setup second stage OnSecondStage: sleep(1800); soundeffectall("ML/ST02.ogg",0,"001-15"); unittalk(.HERO, "Wait. Someone is here."); maptimer("001-15", 2000, "shake::OnGM", true); sleep(2500); soundeffectall("ML/Roar.ogg",0,"001-15"); mapannounce("001-15", "Moubootaur : ##BROAARRRRRRR!", 0); sleep(1500); soundeffectall("ML/ST03.ogg",0,"001-15"); unittalk(.HERO, "For the record, I cannot intervene in this fight due to my current state!"); maptimer("001-15", 5000, "shake::OnGM", true); sleep(5000); // Spawn... The Moubootaur! Finally! soundeffectall("ML/ML01.ogg",0,"001-15"); mapannounce("001-15", "Moubootaur : ##BTALPANS!!", 0); .ML=monster("001-15", 54, 37, "The Moubootaur", MobMoubootaur, 1, "#Moubootaur::OnEpilogue"); immortal(.ML); sc_start SC_STUN, 99990, 1, 10000, SCFLAG_NOAVOID|SCFLAG_FIXEDTICK, .ML; sleep(3500); soundeffectall("ML/ML02.ogg",0,"001-15"); unittalk(.ML, "I see... You've all come here... TO DIE!"); sleep(5000); soundeffectall("ML/ST04.ogg",0,"001-15"); if (.wh) unittalk(.HERO, sprintf("%s, say something! You are the world's hero, not me!", $WORLD_HERO$)); else unittalk(.HERO, sprintf("Damn, where is %s to make the heroic speech! It's the Moubootaur you're about to face!", $WORLD_HERO$)); sleep(8000); soundeffectall("ML/ML03.ogg",0,"001-15"); unittalk(.ML, "It matters not... The Mana Source holds no special powers in this place..."); sleep(8000); soundeffectall("ML/ML04.ogg",0,"001-15"); unittalk(.ML, "Come and face me, those whom survive may even get the honor of becoming Mouboos after I'm done with you! HAHAHAH!"); sleep(9000); enablenpc "#ML_EastWarp"; soundeffectall("ML/ST05.ogg",0,"001-15"); unittalk(.HERO, "Watch out, this is not the only active battle front! The Alliance is now clashing with the Moubootaur's followers to the east!"); sleep(19000); soundeffectall("ML/ML05.ogg",0,"001-15"); unittalk(.ML, "I hope everyone is ready... Because the battle..."); sleep(3000); soundeffectall("ML/ML06.ogg",0,"001-15"); unittalk(.ML, "Begins ##BNOW!##b"); // Allow the Moubootaur to use magic, reset turn count, start battleground .canAttack = true; .battleGrd = true; .turn = 0; .tbet = 8; .bgBonus = 0; initnpctimer; //$@ML_SHOWDOWN = 3; // Assign definitive stats to the Moubootaur setunitdata(.ML, UDT_LEVEL, 300); setunitdata(.ML, UDT_STR, 255); setunitdata(.ML, UDT_AGI, 255); setunitdata(.ML, UDT_VIT, 255); setunitdata(.ML, UDT_INT, 255); setunitdata(.ML, UDT_DEX, 255); setunitdata(.ML, UDT_LUK, 255); setunitdata(.ML, UDT_ADELAY, 1600); setunitdata(.ML, UDT_ATKRANGE, 4); // Battle Status (10M hp; but other stats need tweaking) setunitdata(.ML, UDT_MAXHP, 9999999); setunitdata(.ML, UDT_HP, 9999999); setunitdata(.ML, UDT_ATKMIN, 4500); setunitdata(.ML, UDT_ATKMAX, 6500); setunitdata(.ML, UDT_DEF, 750); setunitdata(.ML, UDT_MDEF, 400); setunitdata(.ML, UDT_HIT, 9999); setunitdata(.ML, UDT_FLEE, 600); setunitdata(.ML, UDT_CRIT, 150); // Reconfigure the AI .@opt=getunitdata(.ML, UDT_MODE); // Disable looting if (.@opt & MD_LOOTER) .@opt=.@opt^MD_LOOTER; // Add knockback immunity .@opt=.@opt|MD_NOKNOCKBACK; // Mark as boss .@opt=.@opt|MD_BOSS; // Mark as aggressive .@opt=.@opt|MD_AGGRESSIVE; .@opt=.@opt|MD_ANGRY; // Make it more op .@opt=.@opt|MD_DETECTOR; .@opt=.@opt|MD_CASTSENSOR_CHASE; .@opt=.@opt|MD_CASTSENSOR_IDLE; .@opt=.@opt|MD_CHANGECHASE; .@opt=.@opt|MD_CHANGETARGET_MELEE; .@opt=.@opt|MD_CHANGETARGET_CHASE; setunitdata(.ML, UDT_MODE, .@opt); end; // The Moubootaur has decided to change platform OnThirdStage: .warpsOnline = false; .canAttack = false; soundeffectall("ML/ML07.ogg",0,"001-15"); mapannounce( "001-15", "Moubootaur : Hah, I was sure you would all die in one hit, but it seems you will be able to at least entertain me!", 0); sleep(8500); soundeffectall("ML/ML08.ogg",0,"001-15"); mapannounce( "001-15", "Moubootaur : But this is strange... You shouldn't have been able to harm me, yet it seems I bleed?", 0); sleep(7500); soundeffectall("ML/ML09.ogg",0,"001-15"); mapannounce( "001-15", "Moubootaur : Unfortunately for you, I hold total control over Aeros Island... including the warp you used to reach me.", 0); sleep(7500); soundeffectall("ML/ML10.ogg",0,"001-15"); mapannounce( "001-15", "Moubootaur : Yes, that's right! I'll slowly destroy you with long range spells while you're stuck! HAHAHAHAH!", 0); sleep(9500); // The Moubootaur can start attacking... NOW! .canAttack = true; soundeffectall("ML/ST06.ogg",0,"001-15"); unittalk(.HERO, "Don't worry, I've been here for months, I know a trick to make the warps work again."); sleep(6500); soundeffectall("ML/ST07.ogg",0,"001-15"); unittalk(.HERO, "But each platform has its own gimmick, so every time he changes platform, you'll need to do something different to make the warps work again."); sleep(6500); unitwarp(getnpcid("Ars Statue"), "001-15", 54, 36); soundeffectall("ML/ST08.ogg",0,"001-15"); unittalk(.HERO, "Now, see that statue which just showed up out of thin air? Yes, not from thin air, I did that."); sleep(6500); soundeffectall("ML/ST09.ogg",0,"001-15"); unittalk(.HERO, "Now, each statue has a challenge, it may be killing something, lockpicking, or even fishing."); sleep(6500); soundeffectall("ML/ST10.ogg",0,"001-15"); unittalk(.HERO, "Once anyone completes it, the portals get enabled for everyone."); sleep(6500); soundeffectall("ML/ST11.ogg",0,"001-15"); unittalk(.HERO, b("What are you waiting for? Go talk to the Statue and hunt the Moubootaur down!")); donpcevent "Ars Statue::OnDisplay"; $@ML_SHOWDOWN = 3; initnpctimer; end; // TODO: The Moubootaur is back to central platform and his HP, spent OnFourthStage: .canAttack = false; immortal(.ML); sc_start SC_STUN, 86400000, 1, 10000, SCFLAG_NOAVOID|SCFLAG_FIXEDTICK, .ML; soundeffectall("ML/ML11.ogg",0,"001-15"); unittalk(.ML, "Hmm. You talpans are fighting more seriously than I anticipated."); sleep(8500); soundeffectall("ML/ML12.ogg",0,"001-15"); unittalk(.ML, "Seems like I'll not be allowed to do whatever I want without you being given a chance to resist."); sleep(8500); soundeffectall("ML/ML13.ogg",0,"001-15"); unittalk(.ML, "Unworthy creatures given the chance to be a challenge to me... 'Tis is truly a joke."); sleep(6500); soundeffectall("ML/ML14.ogg",0,"001-15"); unittalk(.ML, "At this rate I won't complete the grand spell. But I can do something else."); sleep(6500); soundeffectall("ML/ML15.ogg",0,"001-15"); unittalk(.ML, "I'll need just a few more minutes to finish it. So I cannot keep you lousy kids entertained any longer."); sleep(6500); soundeffectall("ML/ML16.ogg",0,"001-15"); unittalk(.ML, "So on the meanwhile, why don't you..."); sleep(6500); soundeffectall("ML/ML17.ogg",0,"001-15"); unittalk(.ML, "...try to stop Aeros from crashing in Tulimshar while I prepare? MUAHAHAH!"); sleep(3500); // Warp Moubootaur to (54,26), where he'll wait // And begin the Aeros Fall, announcing to all NPCs $@ML_SHOWDOWN = 4; //mapannounce("Moubootaur : *poofs*") unitwarp(.ML, "001-15", 54, 26); unittalk(.ML, "*poofs*"); sleep(2500); soundeffectall("ML/SOS01.ogg",0); kamibroadcast(col("The Alliance's High Council has decreed a world-wide state of emergency.", 1)); sleep(3500); soundeffectall("ML/ST12.ogg",0,"001-15"); unittalk(.HERO, "Well. That's not good. Aeros is massive, this is going to be worse than the Great Fire ever was."); sleep(5500); soundeffectall("ML/ST13.ogg",0,"001-15"); unittalk(.HERO, "If Aeros falls over Tulimshar... I don't think Talpans can survive."); sleep(3200); soundeffectall("ML/SOS02.ogg",0); kamibroadcast(col("According to authorities, a massive island is falling from skies, which is likely to cause Talpans to go extinct.", 1)); sleep(3500); // Maybe create Elli? Rejected. soundeffectall("ML/ST14.ogg",0,"001-15"); unittalk(.HERO, "Well, what are you waiting for? I'm already dead AND retired!"); sleep(4500); // Define the switches starting state. This is Chase the Lights or Lights Out setarray $@ML_AEROS_X0, 1, any(0,1), any(0,1), any(0,1), any(0,1); setarray $@ML_AEROS_X1, any(0,1), any(0,1), 1, any(0,1), any(0,1); setarray $@ML_AEROS_X2, any(0,1), any(0,1), any(0,1), 0, any(0,1); setarray $@ML_AEROS_X3, any(0,1), any(0,1), any(0,1), any(0,1), any(0,1); setarray $@ML_AEROS_X4, any(0,1), 0, any(0,1), any(0,1), any(0,1); // Enable, but do not activate, all switches freeloop(true); for (.@y=0; .@y < 5; .@y++) { for (.@x=0; .@x < 5; .@x++) { .@my$ = sprintf("$@ML_AEROS_X%d[%d]", .@y, .@x); .@nm$ = sprintf("Switch#ML_%d_%d", .@y, .@x); .@cur = getd(.@my$); enablenpc .@nm$; if (.@cur) setnpcdisplay .@nm$, NPC_SWITCH_ONLINE; else setnpcdisplay .@nm$, NPC_SWITCH_OFFLINE; } } freeloop(false); soundeffectall("ML/ST15.ogg",0,"001-15"); unittalk(.HERO, "See these switches? All of them must be facing RIGHT to reset Aeros floaters. But when you turn one on or off, all adjacent switches will also flip. Now good luck!"); // This actually is not strictly necessary but I did a mistake sleep(14500); soundeffectall("ML/ST16.ogg",0,"001-15"); unittalk(.HERO, "(...Maybe I should have warned them that they're slightly misaligned in Y axis, especially the one closest to the entrance... but such trivial difficulty is something they can figure out on their own, right?)"); sc_end SC_STUN, .ML; end; // All switches were flipped OnAerosCutscene: // Disable Eastern Warp and Eastern Battleground .battleGrd = false; .bgBonus = 0; soundeffectall("system/quest-done.ogg",0,"001-15"); // TODO: Play a click and a clank sound, then a vrrrrr // Right now, flipping switches is boring soundeffectall("ML/SOS01.ogg",0); kamibroadcast(col("The crisis has been averted.", 3)); sleep(1800); soundeffectall("ML/ST17.ogg",0,"001-15"); unittalk(.HERO, "Great job! Also, you might not have noticed, but the Eastern Battleground is also over. In general, an astounding victory to the allied forces."); sleep(11500); soundeffectall("ML/ST18.ogg",0,"001-15"); unittalk(.HERO, "I'll now retract the switches. You should all return to the central platform, to face the Moubootaur down."); sleep(5500); // Disable all switches, and unset their sprite freeloop(true); for (.@y=0; .@y < 5; .@y++) { for (.@x=0; .@x < 5; .@x++) { .@nm$ = sprintf("Switch#ML_%d_%d", .@y, .@x); setnpcdisplay .@nm$, NPC_NO_SPRITE; disablenpc .@nm$; } } freeloop(false); sleep(4500); // Condition to goto OnFifthStage // It should bring players to the North Area for the Showdown // FIXME: But what if it the OnDeath event is not fired? .@gen=monster("001-15", 54, 37, "Mouboo Admiral", TopUnderling, 1, "#Moubootaur::OnFifthStage"); _boostMe(.@gen, 30); // The Admiral can attack and be attacked right now soundeffectall("ML/Guard01.ogg",0,"001-15"); unittalk(.@gen, "I shall not let you disturb my liege! Even if this is the last thing I do!"); sleep(3500); soundeffectall("ML/Guard02.ogg",0,"001-15"); unittalk(.@gen, "I am the Mouboo Admiral! Face my wrath, puny creatures!"); // Spawn the reinforcements, but this is just to stall and gain time, so... sleep(100); for (.@i = 0; .@i < 6; .@i++) { .@mob = any( FlyingUnderling, GunnerUnderling, HalberdUnderling, WizardUnderling, BansheeUnderling, HalberdUnderling ); .@m=areamonster("001-15", 49, 33, 59, 41, strmobinfo(1, .@mob), .@mob, 1); _boostMe(.@m); sleep(500); } // Let everyone know the Admiral has shown up mapannounce( "001-15", "The Mouboo Admiral has shown up in the central platform.", 0); end; // All the Distractions are dealt with OnFifthStage: soundeffectall("ML/ML18.ogg",0,"001-15"); unittalk(.ML, "You know, I trusted you."); sleep(4500); soundeffectall("ML/ML19.ogg",0,"001-15"); unittalk(.ML, "I knew you wouldn't have let Aeros fall. You wouldn't kill everyone just to get rid of me."); sleep(6500); soundeffectall("ML/ML20.ogg",0,"001-15"); unittalk(.ML, "And this is why I've won. Come, lets have a final match. Don't hold back. Show me your determination!"); sleep(12500); // TODO: Eliminate part of the collision layer? .canAttack = true; unitwarp(.ML, "001-15", 54, 29); setunitdata(.ML, UDT_MAXHP, 2500000); setunitdata(.ML, UDT_HP, 2500000); setunitdata(.ML, UDT_ADELAY, 1500); setunitdata(.ML, UDT_MDEF, 450); setunitdata(.ML, UDT_CRIT, 200); setunitdata(.ML, UDT_ATKRANGE, 5); soundeffectall("ML/ML21.ogg",0,"001-15"); unittalk(.ML, "Don't run! Come and face me if you're serious about stopping me!"); $@ML_SHOWDOWN = 6; changemusic "001-15", "Slaying_Shovel.ogg"; sleep(500); end; // The Moubootaur vanished, so he either completed the spell or was defeated OnEpilogue: // He completed the spell: Shut down the game server, permanently if ($@ML_SHOWDOWN != 6) { Exception("The Moubootaur **completed** the Grand Spell to shutdown the game server, but Mr. Saves did not allow it to stand, and rewound time.", RB_IRCBROADCAST|RB_GLOBALANNOUNCE|RB_DEBUGMES|RB_PLEASEREPORT); // ERROR killmonsterall("001-15"); goto OnIntroCutscene; // Fail-safe, which resets the showdown atcommand("@serverexit 103"); // It's Canon: *SHUTDOWN* the map server end; } stopnpctimer; $@ML_SHOWDOWN = 7; changemusic "001-15", "epilogue.ogg"; soundeffectall("ML/ML22.ogg",0,"001-15"); mapannounce( "001-15", "Moubootaur : Not bad. And thus, the curtains begin to fall to a close, the epilogue of the Talpan race, their last act of defiance against my person.", 0); sleep(18000); soundeffectall("ML/ML23.ogg",0,"001-15"); mapannounce( "001-15", "Moubootaur : Everything is now in motion. Cherish the few moments you have left, and rejoice knowing you only delayed the inevitable.", 0); sleep(15000); soundeffectall("ML/ML24.ogg",0,"001-15"); mapannounce( "001-15", "Moubootaur : Because I have finally sealed Elli. While I was sealed, Talpans prospered. Now the tables have turned.", 0); sleep(12000); soundeffectall("ML/ML25.ogg",0,"001-15"); mapannounce( "001-15", "Moubootaur : Mouboos will rise to their rightful place, and Talpans will only watch their slow decline.", 0); sleep(8000); setmapmask "001-15", 17; sleep(800); addmapmask "001-15", 131072; sleep(400); soundeffectall("ML/ML26.ogg",0,"001-15"); sleep(1400); addmapmask "001-15", 262144; sleep(400); soundeffectall("ML/ML27.ogg",0,"001-15"); sleep(1400); addmapmask "001-15", 524288; sleep(400); soundeffectall("ML/ML28.ogg",0,"001-15"); sleep(1400); setmapmask "001-15", 17; sleep(800); setmapmask "001-15", 16; // TODO: Clean up and reset everything via function, for defeat failsafe killmonsterall("001-15"); sleep(400); setmapmask "001-15", 1; // Give rewards, revive Skelli when doing so maptimer2("001-15", 10, "#Moubootaur::OnVictory"); end; OnInit: // Global, Ephemeral Statuses .warpsOnline = true; .canAttack = false; .battleGrd = false; .lastHpc = 100; .mapMode = 0; .bgBonus = 0; .MLindex = 0; // The *party* penalty handlers .wh=false; .lb=false; .da=false; .ty=false; .rs=false; .as=false; .bb=false; // Dynamic references .ML=0; .HERO=0; .mana = 0; // More mana = more likely to cast skills .tbet = 0; // Internal counter for turn system .turn = 0; // Actual turn counter // Platforms Cardinal Sequence is // Central, Clock, Wind, Fire, Plague, Cold, Blood, Disease, Curse // XXX: Moubootaur goes from [1] to [8] setarray $@MLFX, 52, 44, 43, 78, 64, 31, 22, 27, 74, 44, 43, 78, 64, 31, 22, 27, 74; setarray $@MLFY, 49, 55, 24, 56, 44, 20, 51, 34, 31, 55, 24, 56, 44, 20, 51, 34, 31; end; } 001-15,55,47,0 script #ML_NorthWarp NPC_ML_CIRCLE,0,0,{ end; OnTouch: if ($@ML_SHOWDOWN < 2) goto L_InitCheck; if (!getvariableofnpc(.warpsOnline, "#Moubootaur")) { soundeffect("ML/PortalOff.ogg",0); dispbottom l("Oh no! The Moubootaur has disabled this portal!"); end; } // All fine, move you and set music slide 54, 29; if ($@ML_SHOWDOWN == 6) changeplayermusic "Slaying_Shovel.ogg"; else changeplayermusic "savior_of_humanity.ogg"; // Kill any stray timer too in the Central Platform // It's overkill and not really necessary, BUT better safe than sorry deltimer("#DungeonCore::OnClocked"); deltimer("#DungeonCore::OnWindy"); deltimer("#DungeonCore::OnHeat"); deltimer("#DungeonCore::OnLeech"); deltimer("#DungeonCore::OnFrost"); deltimer("#DungeonCore::OnBleed"); deltimer("#DungeonCore::OnSick"); deltimer("#DungeonCore::OnCurse"); end; L_InitCheck: // First way to trigger: All keys in a single player inventory if (countitem(MKeyWater) == 1 && countitem(MKeyEarth) == 1 && countitem(MKeyDeath) == 1 && countitem(MKeyFire) == 1 && countitem(MKeyWind) == 1 && countitem(MKeyMana) == 1 && countitem(MKeyEvil) == 1 && countitem(MKeySacred) == 1) goto L_InitDel; // Second (prefered) way to trigger: All keys in the magic circle .@i=0; .@i+=min(1,getareadropitem("001-15", .x-3, .y-3, .x+3, .y+3, MKeyWater, false)); .@i+=min(1,getareadropitem("001-15", .x-3, .y-3, .x+3, .y+3, MKeyEarth, false)); .@i+=min(1,getareadropitem("001-15", .x-3, .y-3, .x+3, .y+3, MKeyDeath, false)); .@i+=min(1,getareadropitem("001-15", .x-3, .y-3, .x+3, .y+3, MKeyFire, false)); .@i+=min(1,getareadropitem("001-15", .x-3, .y-3, .x+3, .y+3, MKeyWind, false)); .@i+=min(1,getareadropitem("001-15", .x-3, .y-3, .x+3, .y+3, MKeyMana, false)); .@i+=min(1,getareadropitem("001-15", .x-3, .y-3, .x+3, .y+3, MKeyEvil, false)); .@i+=min(1,getareadropitem("001-15", .x-3, .y-3, .x+3, .y+3, MKeySacred,false)); if (.@i == 8) goto L_InitOk; soundeffect("ML/CircleOff.ogg",0); dispbottom l("This circle doesn't work right now. Maybe there's a clue on what is needed?"); end; L_InitDel: delitem MKeySacred, 1; delitem MKeyWater, 1; delitem MKeyEarth, 1; delitem MKeyDeath, 1; delitem MKeyFire, 1; delitem MKeyWind, 1; delitem MKeyEvil, 1; delitem MKeyMana, 1; // FALLTHROUGH L_InitOk: $@ML_SHOWDOWN=2; getareadropitem("001-15", .x-3, .y-3, .x+3, .y+3, MKeyWater, true); getareadropitem("001-15", .x-3, .y-3, .x+3, .y+3, MKeyEarth, true); getareadropitem("001-15", .x-3, .y-3, .x+3, .y+3, MKeyDeath, true); getareadropitem("001-15", .x-3, .y-3, .x+3, .y+3, MKeyFire, true); getareadropitem("001-15", .x-3, .y-3, .x+3, .y+3, MKeyWind, true); getareadropitem("001-15", .x-3, .y-3, .x+3, .y+3, MKeyMana, true); getareadropitem("001-15", .x-3, .y-3, .x+3, .y+3, MKeyEvil, true); getareadropitem("001-15", .x-3, .y-3, .x+3, .y+3, MKeySacred,true); // Send the hero to the warp circle .@hero = getvariableofnpc(.HERO, "#Moubootaur"); unitwalk(.@hero, 55, 47); // Mark the activation sleep2(50); specialeffect(312, AREA, .name$); // Enable the warp and NPC dialogue sleep2(300); soundeffect("ML/CircleOK.ogg",0); unittalk(.@hero, "Good job! Chop chop!"); // Warp NPC and activator to the platform sleep2(250); unitwarp(.@hero, "001-15", 55, 29); //sc_start SC_STUN, 86400000, 1, 10000, SCFLAG_NOAVOID|SCFLAG_FIXEDTICK, .@hero; // Begin 2nd Stage Cutscene donpcevent "#Moubootaur::OnSecondStage"; goto OnTouch; end; OnInit: end; } 001-15,54,28,0 script #ML_NorthExit NPC_HIDDEN,0,0,{ end; OnTouch: if ($@ML_SHOWDOWN < 2) end; if (!getvariableofnpc(.warpsOnline, "#Moubootaur")) { soundeffect("ML/PortalOff.ogg",0); dispbottom l("Oh no! The Moubootaur has disabled this portal!"); end; } // All fine, move you and set music slide 55, 48; changeplayermusic "around_the_world.ogg"; end; L_InitCheck: end; OnInit: end; } 001-15,60,53,0 script #ML_EastWarp NPC_SUMMONING_CIRC,0,0,{ end; OnTouch: // Verify if this portal is active (it should, however, be disabled!) if ($@ML_SHOWDOWN < 2) goto L_Inactive; if (!getvariableofnpc(.battleGrd, "#Moubootaur")) end; // Assuming nothing is wrong, then do the warp slide 85, 57; changeplayermusic "the_fire_dragon.ogg"; end; // Report the error to the user & console L_Inactive: dispbottom l("There's nothing of interest in this direction... for now."); consolewarn("ML Showdown: Eastern Portal was visible while disabled."); end; OnInit: end; } // You can always leave the battleground area, there's no restriction whatsoever 001-15,85,58,0 script #ML_EastExit NPC_SUMMONING_CIRC,0,0,{ end; OnTouch: slide 60, 54; changeplayermusic "around_the_world.ogg"; end; OnInit: end; } /* Miller system (otherwise, you can't reach the switches & platforms) */ 001-15,51,49,0 script #MLWA+ NPC_SUMMONING_CIRC,0,0,{ end; OnTouch: // Rate Limiting if (@nowarp >= gettimetick(2)) end; // Verify if portalling is active in the Showdown // Probably via a public function in #Moubootaur if (!getvariableofnpc(.warpsOnline, "#Moubootaur")) { soundeffect("ML/PortalOff.ogg",0); dispbottom l("Oh no! The Moubootaur has disabled this portal!"); end; } if (!$@ML_SHOWDOWN) end; if (!getvariableofnpc(.turn, "#Moubootaur")) end; // Dungeon Climate System (stop effect, start next) // Disable previous Climate System timer, before @state is changed switch (miller_rand(@state, getcharid(0), 17)) { case 1: case 9: deltimer("#DungeonCore::OnClocked"); break; case 2: case 10: deltimer("#DungeonCore::OnWindy"); break; case 3: case 11: deltimer("#DungeonCore::OnHeat"); break; case 4: case 12: deltimer("#DungeonCore::OnLeech"); break; case 5: case 13: deltimer("#DungeonCore::OnFrost"); break; case 6: case 14: deltimer("#DungeonCore::OnBleed"); break; case 7: case 15: deltimer("#DungeonCore::OnSick"); break; case 8: case 16: deltimer("#DungeonCore::OnCurse"); break; default: // Case 0 has no special effects whatsoever break; } // All portals are the same, anyway if (compare(strnpcinfo(2), "+")) @state += 3; else @state -= 2; // Note that miller_rand can still give adjacent values! .@index = miller_rand(@state, getcharid(0), 17); sleep2(15); // FIXME: Sometimes the slide triggers the next slide or fail to resync // So I replaced slide() with warp() //slide $@MLFX[.@index], $@MLFY[.@index]; warp "001-15", $@MLFX[.@index], $@MLFY[.@index]; // This code also controls map effects and music! switch (.@index) { case 1: case 9: changeplayermusic "last_minute.ogg"; break; case 2: case 10: changeplayermusic "adonthell.ogg"; break; case 3: case 11: changeplayermusic "Arabesque.ogg"; break; case 4: case 12: changeplayermusic "valkyries.ogg"; break; case 5: case 13: changeplayermusic "misuse.ogg"; break; case 6: case 14: changeplayermusic "forest-sprint.ogg"; break; case 7: case 15: changeplayermusic "broken_kingdom.ogg"; break; case 8: // Curse case 16: changeplayermusic "let_the_battles_begin.ogg"; break; default: changeplayermusic "around_the_world.ogg"; break; } @nowarp=gettimetick(2)+1; // Dungeon Climate System (start effect, stop previous) switch (.@index) { case 1: case 9: addtimer(10, "#DungeonCore::OnClocked"); break; case 2: case 10: addtimer(10, "#DungeonCore::OnWindy"); break; case 3: case 11: addtimer(10, "#DungeonCore::OnHeat"); break; case 4: case 12: addtimer(10, "#DungeonCore::OnLeech"); break; case 5: case 13: addtimer(10, "#DungeonCore::OnFrost"); break; case 6: case 14: addtimer(10, "#DungeonCore::OnBleed"); break; case 7: case 15: addtimer(10, "#DungeonCore::OnSick"); break; case 8: case 16: addtimer(10, "#DungeonCore::OnCurse"); break; default: // Case 0 has no special effects whatsoever break; } end; } //001-15,28,136,0 duplicate(#MLWA+) #MLWA- NPC_SUMMONING_CIRC,0,0 // Clock, Wind, Fire, Plague, Cold, Blood, Disease, Curse // B, C, D, E, F, G, H, I 001-15,34,44,0 duplicate(#MLWA+) #MLWB+ NPC_NO_SPRITE,0,0 001-15,45,55,0 duplicate(#MLWA+) #MLWB- NPC_NO_SPRITE,0,0 001-15,36,38,0 duplicate(#MLWA+) #MLWC+ NPC_SUMMONING_CIRC,0,0 001-15,44,24,0 duplicate(#MLWA+) #MLWC- NPC_SUMMONING_CIRC,0,0 001-15,69,53,0 duplicate(#MLWA+) #MLWD+ NPC_SUMMONING_CIRC,0,0 001-15,78,55,0 duplicate(#MLWA+) #MLWD- NPC_SUMMONING_CIRC,0,0 001-15,75,38,0 duplicate(#MLWA+) #MLWE+ NPC_SUMMONING_CIRC,0,0 001-15,63,44,0 duplicate(#MLWA+) #MLWE- NPC_SUMMONING_CIRC,0,0 001-15,21,24,0 duplicate(#MLWA+) #MLWF+ NPC_SUMMONING_CIRC,0,0 001-15,31,22,0 duplicate(#MLWA+) #MLWF- NPC_SUMMONING_CIRC,0,0 001-15,31,59,0 duplicate(#MLWA+) #MLWG+ NPC_SUMMONING_CIRC,0,0 001-15,21,51,0 duplicate(#MLWA+) #MLWG- NPC_SUMMONING_CIRC,0,0 001-15,22,43,0 duplicate(#MLWA+) #MLWH+ NPC_SUMMONING_CIRC,0,0 001-15,28,34,0 duplicate(#MLWA+) #MLWH- NPC_SUMMONING_CIRC,0,0 001-15,65,26,0 duplicate(#MLWA+) #MLWI+ NPC_SUMMONING_CIRC,0,0 001-15,75,31,0 duplicate(#MLWA+) #MLWI- NPC_SUMMONING_CIRC,0,0 // Main Room Traps, only against players 001-15,0,0,0 script #ML_Trap01 NPC_TRAP_ONLINE,0,0,{ end; OnTouch: OnTouchNPC: WorldHeartTrap(); sleep(500); // Wait 500ms for animation setnpcdisplay .name$, NPC_TRAP_ONLINE; // Move the trap away after it disarms (up to 30 attempts) OnInit: .@e=0; do { if (.@e >= 30) break; .@x = rand2(48, 60); .@y = rand2(30, 44); .@e+=1; } while (!checknpccell("001-15", .@x, .@y, cell_chkpass)); movenpc .name$, .@x, .@y; end; } 001-15,0,0,0 duplicate(#ML_Trap01) #ML_Trap02 NPC_TRAP_ONLINE,0,0 001-15,0,0,0 duplicate(#ML_Trap01) #ML_Trap03 NPC_TRAP_ONLINE,0,0 001-15,0,0,0 duplicate(#ML_Trap01) #ML_Trap04 NPC_TRAP_ONLINE,0,0 001-15,0,0,0 duplicate(#ML_Trap01) #ML_Trap05 NPC_TRAP_ONLINE,0,0 001-15,0,0,0 duplicate(#ML_Trap01) #ML_Trap06 NPC_TRAP_ONLINE,0,0 001-15,0,0,0 duplicate(#ML_Trap01) #ML_Trap07 NPC_TRAP_ONLINE,0,0 // Convenience Shop NPC // TODO: enablenpc; and disablenpc; depending on state? Move to (58,50)? // It sells Stat Reset Potions, and that's how you're supposed to reset stats. 001-15,50,47,0 script Tiki NPC_TIKI,{ public function tikiInfo; mesn; mes l("Hey hey, my name is Micksha, and I'm Arthur's grandfather!"); mes l("I retired to my own laboratory and sell strange drinks on the side!"); mes l("The Mirror Lake here is twisty, so I can do things for you... for a price!"); if (Class == Skelli) { mesc l("Unfortunately though, you are dead. You should seek your body where you left it, before talking to me!"), 1; // Maybe offer to give the body coordinates? close; } next; // Pricing .@price = 15; if (!MOUBOOTAUR_WINNER) .@price += 150; if (!EPISODE_WINNER) .@price += 150; if (islegendary()) .@price += 65; mesc l("Special Price for You! %s GP!", fnum(.@price)), 1; // Friendly Advise, "free" of charge if (any(true, false)) mesc l("* \"Make sure to be well stocked and stashed for the fights! Preparation is the utmost for victory!\" - The Micksha"); else mesc l("* \"Prepare, go and destroy! Don't tell your parents I've said that!\" - The Micksha"); // Require the money NOW if (Zeny >= .@price) Zeny -= .@price; else if (BankVault >= .@price) BankVault -= .@price; else close; // Main Menu select l("I don't need anything!"), l("Withdraw Money"), rif($@ML_SHOWDOWN < 2, l("Open Storage")), rif($@ML_SHOWDOWN < 6, l("Nursery services")), rif($@ML_SHOWDOWN < 4, l("Acquire goods")), l("Help, I'm lost and I want to pay for information!"), l("What penalties are applied on me?"); switch (@menu) { case 2: Banking(); break; case 3: Banker("Micksha", "Impregnable Fortress", 999999); break; case 4: Nurse("Micksha", 10, 6); break; case 5: closeclientdialog; openshop(.name$); break; case 6: tikiInfo(); break; case 7: closeclientdialog; @ml_penwarn=false; "#Moubootaur"::_calcDebuff(0); break; default: Zeny += .@price; break; } close; // This is also used by the Grimorium (and for free, even) // Theoretically, "default" can be triggered by Grimorium (and it IS a bug) public function tikiInfo { mesc l("If you die in this map, you'll become a Skeleton unable to fight back unless you find your corpse."); dnext; mesc l(" In rare cases though, you might end up in a maze. The Obelisks there will be able to revive you, there's no escaping otherwise."); dnext; switch ($@ML_SHOWDOWN) { case 1: mesc l("You should find a way to activate the warp portal to the north."); mesc l("Most likely, finding key items in the platforms and dropping them in the portal all at once will work?"); break; case 2: mesc l("Try using the warp portal to the north. But be prepared for a fight."); mesc l("Alternatively, head east to aid the Alliance and those whom die in this map."); mesc l("From this point onward, I no longer offer storage services."), 1; break; case 3: mesc l("Keep pursuing the Moubootaur through the platforms."); mesc l("Alternatively, head east to aid the Alliance and those whom die in this map."); break; case 4: mesc l("The Moubootaur used a distraction. He must be plotting something now that he saw he can lose."); mesc l("Solve the distraction. This is done by having all switches flipped to right (\"ON\")."); mesc l("Remember: Flipping a switch will flip all adjacent switches, even if they're in a different platform."); mes ""; mesc l("Alternatively, head east to aid the Alliance and those whom die in this map."); mesc l("From this point onward, I no longer offer shop services."), 1; break; case 5: mesc l("I don't know. You should head to northern platform."); mesc l("It's no longer possible to go to the eastern platform; if it was not cleared, consider mailing grenades so dead players can return."); break; case 6: mesc l("All bets are off! Do your best to defeat the Moubootaur!"); mesc l("From this point onward, I no longer offer nurse services."), 1; break; case 7: mesc l("Enjoy the Epilogue."), 1; break; default: mesc l("I don't know. It might be a bug."); break; } return; } // x2( Item , Multiplier=2x ) function x2 { return getiteminfo(getarg(0), ITEMINFO_BUYPRICE) * getarg(1, 2); } OnInit: .sex = G_MALE; .distance = 3; sellitem InsuranceContract, x2(InsuranceContract, 1); sellitem ChamomileTea, x2(ChamomileTea, 4); // Mana F sellitem YerbaMate, x2(YerbaMate, 1); // Mana S sellitem LemonCake, x2(LemonCake, 3); // Homun F sellitem WhiteCake, x2(WhiteCake, 5); // Homun A sellitem PiberriesInfusion, x2(PiberriesInfusion, 3); sellitem FatesPotion, x2(FatesPotion, 3); sellitem ClothoLiquor, x2(ClothoLiquor, 3); sellitem LachesisBrew, x2(LachesisBrew, 3); sellitem AtroposMixture, x2(AtroposMixture, 3); sellitem TrainingAmmoBox, x2(TrainingAmmoBox); // Arrow E sellitem IronAmmoBox, x2(IronAmmoBox); // Arrow C sellitem BigBulletSack, x2(BigBulletSack, 1); // Bullets MAX sellitem StatusResetPotion, 4999; sellitem MercBoxA, 9999; sellitem BottleOfSewerWater, 9999; sellitem BlueberryCake, 9999; sellitem SmokeGrenade, 9999; sellitem ScentGrenade, 9999; sellitem HerbalTea, 9999; sellitem EmptyBox, 9999; sellitem Coffee, 9999; sellitem IcedBottle, 9999; sellitem PurificationPotion, 9999; sellitem ScrollSCave, 9999; sellitem CommonCarp, 9999; sellitem FishingRod, 9999; sellitem TreasureKey, 9999; sellitem SaxsoKey, 9999; sellitem Flour, 9999; sellitem StrangeCoin, 9999; sellitem GuildCoin, 9999; sellitem HeroCoin, 9999; sellitem Lifestone, 9999; sellitem Quill, 9999; sellitem EverburnPowder, 9999; sellitem EarthPowder, 9999; sellitem WoodenLog, 9999; sellitem MinerKnife, 9999; sellitem ShortBow, 9999; sellitem Lockpicks, 9999; sellitem IronShovel, 9999; sellitem EmptyBottle, 12999; sellitem ArcmageBoxset, 14999; sellitem LeatherQuiver, 19999; sellitem HomunResetPotion, 19999; sellitem DeathPotion, 19999; sellitem NymphPoison, 19999; sellitem BrokenWarpCrystal, 19999; sellitem SacredImmortalityPotion, 29999; sellitem GoldenApple, 49999; sellitem ElixirOfLife, 99999; sellitem MagicApple, 199999; sellitem Pickaxe, 299999; sellitem LegendaryTortuga, 1499999; sellitem PiouEgg, 3499999; sellitem Skypiercer, 9999999; sellitem BlackyCatFix, INT_MAX/2; end; } function script ML_MobKill { // Moubootaur Showdown Effects if (getvariableofnpc(.mapMode, "#Moubootaur") == ML_MAPMODE_PAY2KILL) { // Not a target of the map-wide effect if (getmap() != "001-15") return; // Price is 10 GP per monster level (~3000 gp for Tortuga) .@price = strmobinfo(3,killedrid) * 10; .@price += rand2(JobLevel); .@debit = 0; // Pay the money or set .@debit if (Zeny >= .@price) Zeny -= .@price; else .@debit = .@price - Zeny; // For each unpaid GP, you lose 1% HP if (.@debit) { Zeny = 0; percentheal -(.@debit), -(.@debit); } } return; } // Duplicates with player name#ML!gid // We truncate player name? 001-15,0,0,0 script Dead_Body#ML!000000 NPC_HIDDEN,0,0,{ end; OnTouch: if (!.dead) end; // Retrieve parameters // The ! character is restricted, so we don't need to use getarraysize() explode(.@parts$, strnpcinfo(0), "!"); .@gid=atoi(.@parts$[1]); // Only the body owner can do this if (getcharid(0) != .@gid) end; // Revive the character "#Moubootaur"::ReviveHandler(false); // Inactivate the body .dead = false; // Move it to somewhere it won't bother unitwarp(getnpcid(), "001-15", 1, 1); // Remove from our internal control array_remove($@ML_DUPES, getcharid(0)); // Ask the body to be removed .@t = htget($@_DUPES, .name$, 0); if (.@t) { .@t = min(gettimetick(2)+2, .@t); htput($@_DUPES, .name$, .@t); } end; OnInit: .dead = true; end; } 001-15,1,1,0 script Andrei NPC_ML_ANDREI,{ npctalk3 col(l("Talk to Tiki if you need help or information."), 9); end; OnInit: npcsit; end; } /* .:: NOT YET IMPLEMENTED ::. Door's Conditional Enable/Disable (reflect on western warp too!) I need code injections for the other challenges ML_CH_FISHING Fishing Conditional ML_CH_COOKING In-Promptu Cooking with special recipe and fail chance (optional) (must equip) -> Copy from cooking code, but provide a specific recipe and check only for it -> (Including seasoning!) -> Which materials, though? ML_CH_CRAFTING In-Promptu Crafting with special recipe and fail chance (optional) (must equip) -> Copy from smith code, but provide a specific recipe and check only for it -> Which materials, though? ML_CH_CHANTING Formulate a random chant based on random clues (optional) [ ] Select random words and verbs and glue them together for password [ ] Add the listener, it can be on the statue itself for hearing range [ ] Hints about the selected words They should be in such way that reading code doesn't give away the answer ML_CH_MUSIC TMW-BR Christmas Bells challenge/miniquest (optional) [ ] */ 001-15,1,1,0 script Ars Statue NPC_FAKIR,{ public function Digging; public function Challenge; // Initial checks and, if necessary, corrections if ($@ML_SHOWDOWN != 3) end; if (.state >= getvariableofnpc(.MLindex, "#Moubootaur")) goto OnCorrect; // Select the challenge .@challenge = miller_rand(.state, $@ML_START, .challenges); switch (.@challenge) { ///////////////////////////////// case ML_CH_LOCKPICKING: mesn l("The %s Challenge", b(l("Lockpicking"))); if (LockPicking(7, 5, 5)) goto OnAdvance; break; ///////////////////////////////// case ML_CH_KINGSQUAD: mesn l("The %s Challenge", b(l("King's Squad"))); next; if (mobcount("001-15", .name$+"::OnSquad")) close; npctalk "Survive!"; monster("001-15", .x, .y, "King's Challenger", Birb, 1, .name$+"::OnSquad"); monster("001-15", .x, .y, "King's Challenger", TerraniteKing, 1, .name$+"::OnSquad"); monster("001-15", .x, .y, "King's Challenger", YetiKing, 1, .name$+"::OnSquad"); monster("001-15", .x, .y, "King's Challenger", MonsterGeneral, 1, .name$+"::OnSquad"); monster("001-15", .x, .y, "King's Challenger", BanditLord, 1, .name$+"::OnSquad"); break; ///////////////////////////////// case ML_CH_EMPERORSQUAD: mesn l("The %s Challenge", b(l("Emperor's Squad"))); next; if (mobcount("001-15", .name$+"::OnSquad")) close; npctalk "Survive!"; monster("001-15", .x, .y, "Emperor's Challenger", SnowmanBoss, 1, .name$+"::OnSquad"); monster("001-15", .x, .y, "Emperor's Challenger", PinkieEmperor, 1, .name$+"::OnSquad"); monster("001-15", .x, .y, "Emperor's Challenger", PsiConscience, 1, .name$+"::OnSquad"); monster("001-15", .x, .y, "Emperor's Challenger", NightDragon, 1, .name$+"::OnSquad"); monster("001-15", .x, .y, "Emperor's Challenger", SpiderQueen, 1, .name$+"::OnSquad"); break; ///////////////////////////////// case ML_CH_MINING: mesn l("The %s Challenge", b(l("Mining"))); next; if (mobcount("001-15", .name$+"::OnBif")) close; npctalk "Use the Pickaxe!"; monster("001-15", .x, .y, "Aeros Bif", AerosBif, 7, .name$+"::OnSquad"); break; ///////////////////////////////// case ML_CH_DIGGING: mesn l("The %s Challenge", b(l("Digging"))); next; break; ///////////////////////////////// default: setnpcdisplay .name$, NPC_FAKIR; mesn l("The %s Challenge", b(l(""))); mes l("Objective: %s", "XXXXXX"); if (!isequipped(Pickaxe)) close; break; } closeclientdialog; close; // Sub functions OnSquad: if (!mobcount("001-15", .name$+"::OnSquad")) goto OnAdvance; end; OnBif: // Not on this challenge (WTF) if (Challenge() != ML_CH_MINING) end; // Not PC kill: You failed (.x/.y) if (!playerattached()) { monster("001-15", .x, .y, "Aeros Bif", AerosBif, 3, .name$+"::OnBif"); monster("001-15", .x, .y, "Avenger", BansheeUnderling, 1); end; } // Not pickaxe: You failed if (!isequipped(Pickaxe)) { getmapxy(.@m$, .@x, .@y ,0); monster("001-15", .@x, .@y, "Aeros Bif", AerosBif, 3, .name$+"::OnBif"); monster("001-15", .x, .y, "Avenger", BansheeUnderling, 1); end; } // TODO: Handle AOE and magic cases? // If all are slain, it's done if (!mobcount("001-15", .name$+"::OnBif")) goto OnAdvance; end; // Core functions public function Challenge { if ($@ML_SHOWDOWN != 3) return -1; return miller_rand(.state, $@ML_START, .challenges); } public function Digging { if ($@ML_SHOWDOWN != 3) return; if (Challenge() != ML_CH_DIGGING) end; if ((rand2(500) - ((readparam(bInt)+readparam(bLuk)+readparam(bDex))/3)) < 0) goto OnAdvance; return; } OnAdvance: if (playerattached()) { closeclientdialog; detachrid(); } if (.state >= getvariableofnpc(.MLindex, "#Moubootaur")) goto OnCorrect; unittalk(getnpcid(.name$), "Thou hast passed the trial."); .state += 1; set getvariableofnpc(.warpsOnline, "#Moubootaur"), true; sleep(50); unitwarp(getnpcid(.name$), "001-15", 1, 1); goto OnDisplay; OnDisplay: .@challenge = miller_rand(.state, $@ML_START, .challenges); switch (.@challenge) { case ML_CH_FISHING: setnpcdisplay .name$, NPC_STATUE_FISHER; break; case ML_CH_MINING: setnpcdisplay .name$, NPC_STATUE_CONTRIBUTOR; break; case ML_CH_LOCKPICKING: setnpcdisplay .name$, NPC_STATUE_BANKER; break; case ML_CH_DIGGING: setnpcdisplay .name$, NPC_STATUE_FAFA; break; case ML_CH_COOKING: setnpcdisplay .name$, NPC_STATUE_COOK; break; case ML_CH_KINGSQUAD: setnpcdisplay .name$, NPC_STATUE_GUARD; break; case ML_CH_EMPERORSQUAD: setnpcdisplay .name$, NPC_STATUE_EVILMAN; break; case ML_CH_CRAFTING: setnpcdisplay .name$, NPC_STATUE_BACCHUS; break; case ML_CH_CHANTING: setnpcdisplay .name$, NPC_STATUE_WIZARD; break; case ML_CH_MUSIC: setnpcdisplay .name$, NPC_STATUE_MUSIC; break; default: setnpcdisplay .name$, NPC_FAKIR; break; } end; OnReset: .state = 0; setnpcdisplay .name$, NPC_FAKIR; unitwarp(getnpcid(.name$), "001-15", 1, 1); end; OnCorrect: .state = getvariableofnpc(.MLindex, "#Moubootaur") - 1; if (getvariableofnpc(.warpsOnline, "#Moubootaur")) unitwarp(getnpcid(.name$), "001-15", 1, 1); goto OnDisplay; OnInit: .distance=2; .state = 0; setarray .challenges, ML_CH_KINGSQUAD, ML_CH_EMPERORSQUAD, ML_CH_LOCKPICKING, ML_CH_MINING, ML_CH_DIGGING; end; } function script MLSwitchFlip { .@x = getarg(0); .@y = getarg(1); .@my$ = sprintf("$@ML_AEROS_X%d[%d]", .@y, .@x); .@nm$ = sprintf("Switch#ML_%d_%d", .@y, .@x); .@cur = getd(.@my$); if (.@cur) { .@new = 0; .@npc = NPC_SWITCH_OFFLINE; } else { .@new = 1; .@npc = NPC_SWITCH_ONLINE; } setnpcdisplay .@nm$, .@npc; setd(.@my$, .@new); return; } 001-15,23,22,0 script Switch#ML_0_0 NPC_NO_SPRITE,{ if ($@ML_SHOWDOWN != 4) end; if (Class == Skelli) end; if (@MLSwitch > gettimetick(2)) end; // (getnpcclass() == NPC_SWITCH_OFFLINE) explode(.@parts$, strnpcinfo(0), "_"); .@x=atoi(.@parts$[1]); .@y=atoi(.@parts$[2]); // Prevent player from flipping switches too quickly @MLSwitch = gettimetick(2) + rand2(6); // Flip or unflip myself and update array soundeffect("system/click.ogg",0); MLSwitchFlip(.@x, .@y); // Flip adjacent switches if (.@x) MLSwitchFlip(.@x-1, .@y); if (.@x != 4) MLSwitchFlip(.@x+1, .@y); if (.@y) MLSwitchFlip(.@x, .@y-1); if (.@y != 4) MLSwitchFlip(.@x, .@y+1); // Tally for result (array_sum) .@val = 0; .@val += array_sum($@ML_AEROS_X0); .@val += array_sum($@ML_AEROS_X1); .@val += array_sum($@ML_AEROS_X2); .@val += array_sum($@ML_AEROS_X3); .@val += array_sum($@ML_AEROS_X4); if (.@val >= 25) { // Begin the cutscene which will get rid of switches $@ML_SHOWDOWN = 5; donpcevent "#Moubootaur::OnAerosCutscene"; } // Spawn a random Lv 100~150 monster (but not Moubootaur's privates) .@mob = any( NightmareDragon, Snail, PinkieSuseran, Jhon, Mandragora, PinkieMaximus, Junglefowl, Tengu, SuperiorShroom, Nutcracker, SiegeTower, Golem, GreenhornAbomination, ShadowTortuga, WaterElement, FireElement, EarthElement, WindElement, SacredWisp, EvilWisp, PanthomWisp, EpiphanyWisp ); monster(.map$, .x, .y, strmobinfo(1, .@mob), .@mob, 1); end; OnInit: .distance=2; disablenpc .name$; end; } //001-,23,22,0 duplicate(Switch#ML_0_0) Switch#ML_0_0 NPC_NO_SPRITE 001-15,42,24,0 duplicate(Switch#ML_0_0) Switch#ML_0_1 NPC_NO_SPRITE 001-15,53,30,0 duplicate(Switch#ML_0_0) Switch#ML_0_2 NPC_NO_SPRITE 001-15,70,26,0 duplicate(Switch#ML_0_0) Switch#ML_0_3 NPC_NO_SPRITE 001-15,85,25,0 duplicate(Switch#ML_0_0) Switch#ML_0_4 NPC_NO_SPRITE 001-15,27,34,0 duplicate(Switch#ML_0_0) Switch#ML_1_0 NPC_NO_SPRITE 001-15,38,34,0 duplicate(Switch#ML_0_0) Switch#ML_1_1 NPC_NO_SPRITE 001-15,59,35,0 duplicate(Switch#ML_0_0) Switch#ML_1_2 NPC_NO_SPRITE 001-15,73,33,0 duplicate(Switch#ML_0_0) Switch#ML_1_3 NPC_NO_SPRITE 001-15,87,34,0 duplicate(Switch#ML_0_0) Switch#ML_1_4 NPC_NO_SPRITE 001-15,24,40,0 duplicate(Switch#ML_0_0) Switch#ML_2_0 NPC_NO_SPRITE 001-15,45,39,0 duplicate(Switch#ML_0_0) Switch#ML_2_1 NPC_NO_SPRITE 001-15,54,41,0 duplicate(Switch#ML_0_0) Switch#ML_2_2 NPC_NO_SPRITE 001-15,67,39,0 duplicate(Switch#ML_0_0) Switch#ML_2_3 NPC_NO_SPRITE 001-15,83,40,0 duplicate(Switch#ML_0_0) Switch#ML_2_4 NPC_NO_SPRITE 001-15,25,50,0 duplicate(Switch#ML_0_0) Switch#ML_3_0 NPC_NO_SPRITE 001-15,38,48,0 duplicate(Switch#ML_0_0) Switch#ML_3_1 NPC_NO_SPRITE 001-15,53,47,0 duplicate(Switch#ML_0_0) Switch#ML_3_2 NPC_NO_SPRITE 001-15,74,45,0 duplicate(Switch#ML_0_0) Switch#ML_3_3 NPC_NO_SPRITE 001-15,86,46,0 duplicate(Switch#ML_0_0) Switch#ML_3_4 NPC_NO_SPRITE 001-15,29,57,0 duplicate(Switch#ML_0_0) Switch#ML_4_0 NPC_NO_SPRITE 001-15,55,55,0 duplicate(Switch#ML_0_0) Switch#ML_4_1 NPC_NO_SPRITE 001-15,69,55,0 duplicate(Switch#ML_0_0) Switch#ML_4_2 NPC_NO_SPRITE 001-15,78,55,0 duplicate(Switch#ML_0_0) Switch#ML_4_3 NPC_NO_SPRITE 001-15,87,56,0 duplicate(Switch#ML_0_0) Switch#ML_4_4 NPC_NO_SPRITE