summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJesusaves <cpntb1@ymail.com>2025-05-25 14:15:59 -0300
committerJesusaves <cpntb1@ymail.com>2025-05-25 14:15:59 -0300
commit6c2ee10eea2b68295dbda7284988fe0c72f0ac45 (patch)
treead7ed3d19e9df5a9a5555fc309c0e8734b54b5aa
parent3f2d9548fe33f80690c0238dc809faf3b8603bc3 (diff)
downloadserverdata-6c2ee10eea2b68295dbda7284988fe0c72f0ac45.tar.gz
serverdata-6c2ee10eea2b68295dbda7284988fe0c72f0ac45.tar.bz2
serverdata-6c2ee10eea2b68295dbda7284988fe0c72f0ac45.tar.xz
serverdata-6c2ee10eea2b68295dbda7284988fe0c72f0ac45.zip
Some stuff which have been ready for days but I was on medical leave
-rw-r--r--db/re/mob_db.conf131
-rw-r--r--npc/001-15/ctrl.c1011
-rw-r--r--npc/commands/grantpower.txt2
-rw-r--r--npc/items/shovel.txt3
4 files changed, 1086 insertions, 61 deletions
diff --git a/db/re/mob_db.conf b/db/re/mob_db.conf
index 82deb7961..a930b0bc2 100644
--- a/db/re/mob_db.conf
+++ b/db/re/mob_db.conf
@@ -589,7 +589,7 @@ mob_db: (
// 1013 is free
// mob ids which is free: 1013 (pikpik)
// is free: 1057, 1059 (small/big mineral bif)
-// is free: 1453 Bobone 1454 ???
+// is free: 1453 Bobone
{
Id: 1014
SpriteName: "EntAbomination"
@@ -12953,6 +12953,12 @@ mob_db: (
SilverBossGift: 400
BronzeBossGift: 200
}
+ // Slightly vulnerable to whips, fists and Gatling
+ WeaponAttacks: {
+ Whips: 11000
+ NoWeapon: 11000
+ GatlingGuns: 10500
+ }
},
{
Id: 1455
@@ -12997,6 +13003,13 @@ mob_db: (
BronzeBossGift: 3
SilverBossGift: 1
}
+ // Strong Resistance to 1H Swords, Vulnerable to Staff
+ WeaponAttacks: {
+ Daggers: 1500
+ 1HSwords: 1500
+ Katars: 1400
+ Staves: 12000
+ }
},
{
Id: 1456
@@ -13041,6 +13054,13 @@ mob_db: (
BronzeBossGift: 3
SilverBossGift: 1
}
+ // Strong Resistance to 2H Swords, Vulnerable to Shotgun
+ WeaponAttacks: {
+ 2HMaces: 1500
+ 2HSpears: 1500
+ Knuckles: 1500
+ Shotguns: 12000
+ }
},
{
Id: 1457
@@ -13085,6 +13105,14 @@ mob_db: (
BronzeBossGift: 3
SilverBossGift: 1
}
+ // Strong Resistance to Staff, Vulnerable to 2H and Rifle
+ WeaponAttacks: {
+ Staves: 1500
+ 2HMaces: 11500
+ 2HSpears: 11500
+ Knuckles: 11500
+ Rifles: 12000
+ }
},
{
Id: 1458
@@ -13129,6 +13157,12 @@ mob_db: (
BronzeBossGift: 3
SilverBossGift: 1
}
+ // Strong Resistance to Bows, Vulnerable to Whip
+ WeaponAttacks: {
+ Bows: 1500
+ Whips: 12000
+ NoWeapon: 11000
+ }
},
{
Id: 1459
@@ -13174,6 +13208,101 @@ mob_db: (
BronzeBossGift: 3
SilverBossGift: 1
}
+ // Strong Resistance to 1H Swords, Vulnerable to Rifle
+ WeaponAttacks: {
+ Daggers: 1500
+ 1HSwords: 1500
+ Rifles: 12000
+ GatlingGuns: 11000
+ }
+},
+{
+ Id: 1460
+ SpriteName: "Shadowboo"
+ Name: "Shadowboo"
+ Lv: 200
+ Hp: 136000
+ Exp: 32000
+ JExp: 275
+ AttackRange: 1
+ Attack: [1500, 1750]
+ Def: 442
+ Mdef: 120
+ Race: 8
+ Element: (8, 1)
+ Stats: {
+ Str: 121
+ Agi: 150
+ Vit: 121
+ Int: 121
+ Dex: 150
+ Luk: 121
+ }
+ Mode: {
+ CanMove: true
+ Aggressive: true
+ ChangeChase: true
+ ChangeTargetChase: true
+ ChangeTargetMelee: true
+ CanAttack: true
+ CastSensorChase: true
+ }
+ MoveSpeed: 300
+ AttackDelay: 1200
+ AttackMotion: 672
+ DamageMotion: 200
+ ViewRange: 7
+ ChaseRange: 11
+ Drops: {
+ LimePotion: 360
+ LimePotion: 140
+ BronzeBossGift: 3
+ SilverBossGift: 2
+ }
+ // Strong Resistance to Lightsaber, vulnerable to bows & rifles
+ WeaponAttacks: {
+ Katars: 1400
+ 1HSwords: 9000
+ Bows: 12000
+ Rifles: 15000
+ }
+ // Immune to Darkness
+ EleResist: {
+ Ele_Dark: 0
+ }
+},
+{
+ Id: 1461
+ SpriteName: "AerosBif"
+ Name: "Aeros Bif"
+ Lv: 1
+ Hp: 1
+ Sp: 0
+ Exp: 0
+ AttackRange: 1
+ Attack: [10, 10]
+ Def: 100
+ Mdef: 1
+ Stats: {
+ Str: 1
+ Agi: 50
+ Vit: 1
+ Int: 1
+ Dex: 1
+ Luk: 1
+ }
+ MoveSpeed: 80
+ ViewRange: 13
+ ChaseRange: 12
+ Race: 5
+ Element: (2, 1)
+ Mode: {
+ Plant: true
+ CanMove: true
+ }
+ EleResist: {
+ Ele_All: 0
+ }
},
// System
{
diff --git a/npc/001-15/ctrl.c b/npc/001-15/ctrl.c
index 8e4da57b5..5365dd9af 100644
--- a/npc/001-15/ctrl.c
+++ b/npc/001-15/ctrl.c
@@ -1,7 +1,77 @@
// 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;
@@ -9,7 +79,9 @@
public function DeathHandler;
public function ReviveHandler;
-function _calcDebuff {
+// _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;
@@ -19,6 +91,7 @@ function _calcDebuff {
.@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);
@@ -60,6 +133,7 @@ function _calcDebuff {
}
if (#BETA_REVIVE) {
.@imprecis+=1;
+ // Silent penalty
}
if (!countitem(JesusalvaGrimorium)) {
.@dumbhead+=10;
@@ -69,6 +143,7 @@ function _calcDebuff {
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);
}
@@ -104,7 +179,9 @@ function _calcDebuff {
}
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) {
@@ -112,18 +189,23 @@ function _calcDebuff {
.@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);
@@ -132,6 +214,7 @@ function _calcDebuff {
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;
@@ -173,11 +256,14 @@ function _calcDebuff {
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)
@@ -198,27 +284,36 @@ function _calcDebuff {
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;
@@ -227,9 +322,12 @@ function checkPenalties {
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);
@@ -264,24 +362,32 @@ function _boostMe {
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:
@@ -300,9 +406,11 @@ public function MapMode {
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;
@@ -320,6 +428,7 @@ public function MapMode {
break;
}
+ // Save the new map mode
.mapMode = .@new_mode;
addmapmask "001-15", .@new_mode;
@@ -329,6 +438,30 @@ public function 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);
@@ -339,6 +472,7 @@ OnRev:
atcommand("@alive");
atcommand("@refresh");
+ // If this is your first death EVER, then hold on!
if (!@ml_skelli) {
@ml_skelli=true;
sleep2(50);
@@ -346,20 +480,25 @@ OnRev:
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;
}
@@ -369,6 +508,7 @@ OnRev:
+// The functions which the Generals call when they die
OnBlue:
if (!playerattached()) {
.@x=72; .@y=41;
@@ -461,7 +601,9 @@ OnBlack:
+// Setup first stage
OnFirstStage:
+ // Eliminate all existing keys
DelItemFromEveryPlayer(MKeyWater);
DelItemFromEveryPlayer(MKeyEarth);
DelItemFromEveryPlayer(MKeyFire);
@@ -471,6 +613,7 @@ OnFirstStage:
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);
@@ -527,14 +670,18 @@ OnFirstStage:
.@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;
@@ -546,6 +693,7 @@ OnFirstStage:
+// **MOUBOOTAUR HEARTBEAT**
OnTimer25000:
OnTimer20000:
OnTimer15000:
@@ -553,27 +701,36 @@ OnTimer15000:
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();
- .@amount = ($@ML_SHOWDOWN >= 2 ? 5 : 2);
+ // 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;
@@ -586,8 +743,13 @@ OnTimer10000:
.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$)
@@ -598,6 +760,7 @@ OnTimer10000:
_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);
@@ -605,38 +768,55 @@ OnTimer10000:
}
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);
}
}
- if (.bgBonus > 0)
+ // Announce the new turn begin
+ if (.bgBonus > 0) {
.@msg$ = ", the battle is in ##BTalpans##b favor!";
- else if (.bgBonus < 0)
+ 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)
@@ -645,22 +825,33 @@ OnTimer10000:
.@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
+ .@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;
@@ -671,6 +862,7 @@ OnTimer10000:
.@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
@@ -678,6 +870,7 @@ OnTimer10000:
.@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
@@ -686,20 +879,27 @@ OnTimer10000:
.@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
+ .@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);
@@ -712,27 +912,59 @@ OnTimer10000:
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;
- if (.MLindex == 1) goto OnThirdStage;
+ // 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;
}
- .@m=monster(.@m$, .@x, .@y, "Mouboo", Moubi, 1);
+ // 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) {
- unittalk(.ML, "Sublime Regeration!"); // TODO: Change this to latin
+ 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);
@@ -742,11 +974,36 @@ OnTimer10000:
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);
@@ -754,7 +1011,10 @@ OnTimer10000:
setunitdata(.@mob, UDT_HP, min(.@hp, .@mp));
.@mobid=any(GunnerUnderling, WizardUnderling, BansheeUnderling);
monster(.@m$, .@x, .@y, strmobinfo(1, .@mobid), .@mobid, 1);
- monster(.@m$, .@x, .@y, strmobinfo(1, DeathCat), DeathCat, 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);
@@ -787,7 +1047,7 @@ OnTimer10000:
detachrid();
break;
case 6:
- .@msg$ = sprintf("This battle is over, %s! ##BThunder Neddle##b!", strcharinfo(0, "cursed player", .@target));
+ .@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;
@@ -822,7 +1082,7 @@ OnTimer10000:
attachrid(.@rnd);
percentheal -15, -25;
detachrid();
- .@mobid=any(HalberdUnderling, FlyingUnderling, TopUnderling);
+ .@mobid=any(HalberdUnderling, FlyingUnderling, TopUnderling, Shadowboo);
monster(.@m$, .@x, .@y, strmobinfo(1, .@mobid), .@mobid, 1);
break;
case 12:
@@ -831,7 +1091,7 @@ OnTimer10000:
percentheal -5, -5;
SC_Bonus(.@t, any(SC_BLIND, SC_POISON), 1);
detachrid();
- .@mobid=any(HalberdUnderling, FlyingUnderling, TopUnderling);
+ .@mobid=any(HalberdUnderling, FlyingUnderling, TopUnderling, Shadowboo);
monster(.@m$, .@x, .@y, strmobinfo(1, .@mobid), .@mobid, 1);
break;
case 13:
@@ -843,11 +1103,13 @@ OnTimer10000:
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);
@@ -883,12 +1145,16 @@ OnTimer10000:
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!");
@@ -897,6 +1163,7 @@ OnTimer10000:
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!");
@@ -911,7 +1178,7 @@ OnTimer10000:
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);
+ .@mobid=any(WizardUnderling, BansheeUnderling, TopUnderling, GunnerUnderling, FlyingUnderling, HalberdUnderling, Shadowboo);
monster(.@m$, .@x, .@y, strmobinfo(1, .@mobid), .@mobid, 1);
break;
case 18:
@@ -947,6 +1214,9 @@ OnTimer10000:
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);
@@ -955,35 +1225,47 @@ OnTimer10000:
"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;
@@ -997,15 +1279,22 @@ OnTimer10000:
+// 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");
@@ -1047,12 +1336,21 @@ public function DeathHandler {
_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
@@ -1060,22 +1358,29 @@ public function DeathHandler {
.@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;
}
@@ -1086,8 +1391,13 @@ public function DeathHandler {
+// 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);
@@ -1095,19 +1405,36 @@ public function ReviveHandler {
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
@@ -1119,13 +1446,21 @@ public function ReviveHandler {
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;
@@ -1141,94 +1476,142 @@ OnIntroCutscene:
.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(10000);
+ 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(8000);
+ 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(14000);
+ 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(8000);
+ 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(7000);
+ 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(7500);
+ 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.");
- sleep(6000);
+ /* 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(10000);
+ 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(1500);
+ 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);
+ 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;
- sleep(1500);
+ //sc_end SC_STUN, .HERO;
+ sleep(2500);
goto OnFirstStage;
+// Setup second stage
OnSecondStage:
- sleep(1500);
+ 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);
+ .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(5000);
+ 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(6000);
+ 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(6000);
+ 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);
@@ -1238,6 +1621,7 @@ OnSecondStage:
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);
@@ -1247,13 +1631,19 @@ OnSecondStage:
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;
@@ -1263,73 +1653,193 @@ OnSecondStage:
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(6500);
+ 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(6500);
+ 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(6500);
- .canAttack = true;
+ 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(6500);
+ 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);
- unittalk(.HERO, "But each platform has its own gimmick, so everytime he changes platform, you'll need to do something different to make the warps work again.");
+ 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(6500);
+ 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(6500);
+ 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(4500);
+ 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);
- unittalk(.HERO, "There are two pumps in the plataforms - have someone go there and check them. I don't know. This is WIP. Go to every platform and chant FLOAT FLOAT or whatever. WIP.");
- sleep(6500);
+ // 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(6500);
+ sleep(12500);
+ // TODO: Eliminate part of the collision layer?
.canAttack = true;
unitwarp(.ML, "001-15", 54, 29);
setunitdata(.ML, UDT_MAXHP, 2500000);
@@ -1338,29 +1848,65 @@ OnFifthStage:
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"; // FIXME reset on player move
+ 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(8000);
+ 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(8000);
+ 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(8000);
+ 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);
- //show centered text "== Moubootaur Legends =="
- //show centered text "=== Normal Ending: The Sealed Shrine ==="
+ 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;
@@ -1369,8 +1915,10 @@ OnInit:
.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
@@ -1378,6 +1926,9 @@ OnInit:
.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;
@@ -1389,12 +1940,20 @@ OnInit:
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");
@@ -1406,6 +1965,7 @@ OnTouch:
end;
L_InitCheck:
+ // First way to trigger: All keys in a single player inventory
if (countitem(MKeyWater) == 1 &&
countitem(MKeyEarth) == 1 &&
countitem(MKeyDeath) == 1 &&
@@ -1425,6 +1985,7 @@ L_InitCheck:
.@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;
@@ -1448,14 +2009,21 @@ L_InitOk:
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;
@@ -1469,6 +2037,12 @@ OnInit:
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;
@@ -1524,7 +2098,10 @@ OnTouch:
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;
}
@@ -1533,6 +2110,8 @@ OnTouch:
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:
@@ -1568,9 +2147,14 @@ OnTouch:
@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:
@@ -1633,6 +2217,9 @@ OnTouch:
}
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
@@ -1704,13 +2291,19 @@ OnInit:
// 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 to you... for a price!");
+ 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;
@@ -1739,7 +2332,8 @@ OnInit:
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("Help, I'm lost and I want to pay for information!"),
+ l("What penalties are applied on me?");
switch (@menu) {
case 2:
Banking();
@@ -1757,6 +2351,11 @@ OnInit:
case 6:
tikiInfo();
break;
+ case 7:
+ closeclientdialog;
+ @ml_penwarn=false;
+ "#Moubootaur"::_calcDebuff(0);
+ break;
default:
Zeny += .@price;
break;
@@ -1768,7 +2367,7 @@ OnInit:
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 escapatory otherwise.");
+ 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:
@@ -1778,7 +2377,7 @@ public function tikiInfo {
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 onwards, I no longer offer storage services."), 1;
+ 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.");
@@ -1786,19 +2385,21 @@ public function tikiInfo {
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.");
+ 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 onwards, I no longer offer shop services."), 1;
+ mesc l("From this point onward, I no longer offer shop services."), 1;
break;
- case 5: // FIXME
- mesc l("I don't know. It might be a bug.");
- mesc l("Alternatively, head east to aid the Alliance and those whom die in this map.");
+ 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: // WIP
+ case 6:
mesc l("All bets are off! Do your best to defeat the Moubootaur!");
- mesc l("From this point onwards, I no longer offer nurse services."), 1;
+ mesc l("From this point onward, I no longer offer nurse services."), 1;
break;
- case 7: // WIP
+ case 7:
mesc l("Enjoy the Epilogue."), 1;
break;
default:
@@ -1869,6 +2470,7 @@ OnInit:
sellitem GoldenApple, 49999;
sellitem ElixirOfLife, 99999;
sellitem MagicApple, 199999;
+ sellitem Pickaxe, 299999;
sellitem LegendaryTortuga, 1499999;
sellitem PiouEgg, 3499999;
sellitem Skypiercer, 9999999;
@@ -1902,17 +2504,27 @@ function script ML_MobKill {
+// 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);
@@ -1933,3 +2545,282 @@ OnInit:
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
+
diff --git a/npc/commands/grantpower.txt b/npc/commands/grantpower.txt
index b059a9155..ac1e3aa30 100644
--- a/npc/commands/grantpower.txt
+++ b/npc/commands/grantpower.txt
@@ -99,6 +99,8 @@ function legendaryRodexWarning {
}
OnClock0238:
+ // Disabled on test server
+ if (debug) end;
.@date=gettimetick(2);
.@warn=.@date-(86400*2);
.@kick=.@date-(86400*7);
diff --git a/npc/items/shovel.txt b/npc/items/shovel.txt
index a9a8583de..88163758b 100644
--- a/npc/items/shovel.txt
+++ b/npc/items/shovel.txt
@@ -128,6 +128,8 @@ function script shovel_scatter {
}
}
freeloop(false);
+ // Final Showdown Injection
+ if ($@ML_SHOWDOWN == 3) "Ars Statue"::Digging();
dispbottom l("Sadly, you found nothing but dirt.");
return 0;
}
@@ -296,6 +298,7 @@ OnInit:
// Aeros can be used too (for events)
AddDigRect("001-1", 20, 20, 342, 158);
+ AddDigRect("001-15", 20, 20, 80, 60);
// LoF Areas
AddDigRect("018-1", 20, 20, 80, 80);