// TMW2 Script
// Author:
// Jesusalva
// Description:
// Crazyfefe's Shrine (remastered)
006-10 mapflag zone MMO
026-2 mapflag zone MMO
// We need to refactor this warp system, replace NPC_HIDDEN, and include the boss
006-10,54,58,0 script Blanc NPC_BLANC,{
if (getunittype(.blanc) >= 0) end;
mesc l("Isn't that Blanc? Should we attempt to capture him?");
mesc l("Advised: 6+ players"), 1;
mesc l("Advised: 1+ mage, 1+ tanker, 2+ healers"), 1;
mesc l("No Time Limit"), 1;
mesc l("Enter/Leave after start: %s", b(l("YES"))), 1;
next;
select
l("Not yet."),
l("Bring it on!"),
rif(is_gm(), l("Bring me, my worst nightmare."));
mes "";
if (@menu == 1) {
closeclientdialog;
close;
}
if (@menu == 2)
.hard = false;
else
.hard = true;
// Create the boss
.blanc = monster("006-10", 54, 61, "Blanc Halifax", Blanc, 1);
.@mlt = (.hard ? 15 : 10);
// Basic attributes
.maxhp = (750000 * .@mlt / 10); // 750k ~ 1250k
setunitdata(.blanc, UDT_MAXHP, .maxhp);
setunitdata(.blanc, UDT_HP, .maxhp);
setunitdata(.blanc, UDT_ATKRANGE, (.hard ? 7 : 6));
// Reconfigure the AI
.@opt=getunitdata(.blanc, 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(.blanc, UDT_MODE, .@opt);
// Nerf the damage, but never miss a hit
setunitdata(.blanc, UDT_ATKMIN, 60 * .@mlt / 10); // 60~90 dmg
setunitdata(.blanc, UDT_ATKMAX, 60 * .@mlt / 10);
setunitdata(.blanc, UDT_ADELAY, 2220 / .@mlt * 10); // 2220 or 1480ms
setunitdata(.blanc, UDT_HIT, 2400);
// Boosting the defense is not necessary
// It nerfs weapons to 40% (bows to 20%)
// Then it resists 50% of Neutral element.
// Note it is strong against Water (25% dmg)
// And weak against Fire (snow) and Wind (100% dmg)
// Otherwise, behave as Ghost element
// Make Blanc damn hard to damage until his reinforcements appear
sc_start SC_PRESTIGE, 5000, 120000, 10000, SCFLAG_NOAVOID|SCFLAG_FIXEDTICK|SCFLAG_FIXEDRATE, .blanc;
// TODO: Defense and attributes are wrong?
disablenpc .name$;
initnpctimer;
closeclientdialog;
close;
OnRw:
dispbottom l("Mission accomplished. Well played!");
// Mark as "done" on Mirror Lake Quest Tracker
##01_CRQUEST = ##01_CRQUEST | MLP_CR_DEBUT;
specialeffect(FX_FANFARE, AREA, getcharid(3));
sleep2(15000);
$@EVENT_08 = PORTHOS_ACTIVE;
warp("033-1", 72, 191);
end;
// Fail-safe Mechanism (will never happen)
OnTimer60000:
consolebug("Warning! final fail-safe mechanism triggered to Blanc.");
initnpctimer;
end;
OnTimer25000:
OnTimer15000:
consolewarn("Warning, fail-safe mechanism triggered to Blanc.");
// This is the boss' core
OnTimer5000:
/* Regeneration & Defeat Loop */
.@end = false;
// FIXME: Don't check the WHOLE map, just the current platform?
if (!getmapusers("006-10")) {
.@hp = getunitdata(.blanc, UDT_HP);
.@mh = .maxhp;
.@hp = max(.@mh, .@hp + (.@mh / 500)); // Regenerates 0.2% HP
// Fully healed, players lost
if (.@hp >= .@mh)
.@end = true;
// Regeneration
setunitdata(.blanc, UDT_HP, .@hp);
}
/* Maybe the fight is over */
if (!mobcount("006-10", "all") || getunittype(.blanc) < 0) {
$@EVENT_08 = PORTHOS_BUSY;
maptimer2("006-10", 10, "Blanc::OnRw");
}
if (!mobcount("006-10", "all") || getunittype(.blanc) < 0 || .@end) {
killmonsterall("006-10");
enablenpc .name$;
kamibroadcast("The battle is over!", "Blanc Showdown");
.beats = 0;
stopnpctimer;
end;
}
/* Move platform according to HP threshold */
.@hp = getunitdata(.blanc, UDT_HP) * 10 / .maxhp;
.@state = 9 - limit(0, .@hp, 9); // The platform ID they should be on
debugmes "HP %d / %d (%d%%) (S %d/%d) [%03d@%d]", getunitdata(.blanc, UDT_HP), .maxhp, .@hp, .@state, .state, .beats, .blanc;
if (.state != .@state) {
unittalk(.blanc, "Damn, you guys are tough! I must fall back!");
sleep(100);
unitwarp(.blanc, "006-10", $@BLANC_X[.@state], $@BLANC_Y[.@state]);
sleep(100);
unittalk(.blanc, "Outta the way!");
.state = .@state;
}
/* Prepare some combat data */
// TODO: Maybe not universal? Maybe we should impose a range?
getmapxy(.@m$, .@x, .@y, UNITTYPE_MOB, .blanc);
.@c=getunits(BL_PC, .@pcs, MAX_CYCLE_PC, .@m$);
.@mvp=0;.@rnd=0;.@def=-1;
for (.@i = 0; .@i < .@c; .@i++) {
if (!.@rnd || !rand2(.@c))
.@rnd=.@pcs[.@i];
if (readbattleparam(.@pcs[.@i], UDT_DEF) > .@def) {
if (readparam(Hp, .@pcs[.@i]) < 1) continue;
.@mvp=.@pcs[.@i];
.@def=readbattleparam(.@pcs[.@i], UDT_DEF);
}
}
.beats += 1;
/* Everyone is dead, get rid of their corpses */
if (!.@mvp || !.@rnd) {
mapwarp("006-10", "033-1", 72, 191);
initnpctimer;
end;
}
//debugmes "----------- Skill Loop, beat is %d", .beats % 18;
/* Decide the skill to use based on ~5s beats over 3 minutes */
switch (.beats % 18) {
// (1/6) Summon Reinforcements (every 60s)
case 1:
case 7:
case 13:
unittalk(.blanc, "Come forth, ##BHalifax's Crew##b, do not let these fools arrest your captain!");
specialeffect(FX_MGWARP, AREA, .blanc); // Maybe 65 would also work
sleep(1000);
// Default amount: Half players + Half magnifying HP
.@max = (getmapusers("006-10")/2) + (max(1, (11 - .@hp) / 10)/2);
// Keep boundaries: Never less than 1, never more than 10
.@max = limit(1, .@max, 10);
for (.@i=0; .@i < .@max; .@i++) {
.@mob = any(Thug, Grenadier, Swashbuckler);
monster(.@m$, .@x, .@y, strmobinfo(1, .@mob), .@mob, 1);
}
break;
// (2/6) Tanker (~30s)
case 0:
case 4:
case 6:
case 10:
case 12:
case 16:
specialeffect(FX_ATTACK, AREA, .blanc);
sleep(1000);
if (.@hp < 3) {
// Third Attack Pattern: Judgment
unittalk(.blanc, sprintf("%s cannot stop me! ##BElectric Judgment##b!", strcharinfo(0, "cursed player", .@mvp)));
.@PW=240; .@SPW=60; .@RG=3;
} else if (.@hp < 7) {
// Second Attack Pattern: Holy Light
unittalk(.blanc, sprintf("%s, I'll show you no mercy! ##BThunder Bolt##b!", strcharinfo(0, "cursed player", .@mvp)));
.@PW=125; .@SPW=25; .@RG=1;
} else {
// First Attack Pattern: Napalm Beat
unittalk(.blanc, sprintf("This battle is over, %s! ##BThunder Neddle##b!", strcharinfo(0, "cursed player", .@mvp)));
.@PW=35; .@SPW=5; .@RG=2;
}
.@mtk = calcdmg(.blanc, .@mvp, HARM_MAGI);
.@dmg = .@mtk * .@PW / 100;
.@dsb = .@mtk * .@SPW / 100;
sleep(1000);
specialeffect(FX_LIGHTNING, AREA, .@mvp);
areaharm(.@mvp, .@RG, .@dsb, HARM_MAGI, Ele_Wind, "filter_always", BL_PC|BL_MER|BL_HOM);
harm(.@mvp, .@dmg, HARM_MAGI, Ele_Holy);
break;
// (3/6) Random Target (~60s)
case 2:
case 8:
case 14:
specialeffect(FX_SMOKE, AREA, .blanc);
sleep(1000);
.@time=rand2(18000, 36000) + 10000 - (.@hp * 1000);
// Switch between curse and disable
if (any(true,false)) {
unittalk(.blanc, sprintf("I hereby ##Bcurse##b you, %s!", strcharinfo(0, "cursed player", .@rnd)));
sc_start(SC_CURSE, .@time, 1, 10000, SCFLAG_FIXEDRATE, .@rnd);
} else {
unittalk(.blanc, sprintf("I shall ##Bdisable##b you, %s!", strcharinfo(0, "cursed player", .@rnd)));
sc_start(SC_BLIND, .@time / 2, 1, 10000, SCFLAG_FIXEDRATE, .@rnd);
sc_start(SC_SILENCE, .@time / 2, 1, 10000, SCFLAG_FIXEDRATE, .@rnd);
}
// Second pattern: Bleeding ON
if (.@hp < 7) {
sc_start(SC_BLOODING, 10000, 1, 9000-(.@hp*1000), SCFLAG_FIXEDRATE, .@rnd);
}
specialeffect(FX_LIGHTNING, AREA, .@rnd);
break;
// (4/6) Traps (~60s)
case 3:
case 9:
case 15:
// Determine if there'll be 4 or 2 AoE explosions
.@extra = (.@hp < 5 || .hard);
// Coordinates are between origin and Blanc
// ID 3 and ID 6 were disabled - the platforms are small
.@mx=$@BLANC_X[.@state];
.@my=$@BLANC_Y[.@state];
.@x1=rand2(.@mx, .@x); .@x2=rand2(.@mx, .@x);
.@y1=rand2(.@my, .@y); .@y2=rand2(.@my, .@y);
// TODO: Maybe replace Dummy/EnergyBall with an invisible monster?
.@t1=monster("006-10", .@x1, .@y1, "", EnergyBall, 1);
.@t2=monster("006-10", .@x2, .@y2, "", EnergyBall, 1);
specialeffect(67, AREA, .@t1);
specialeffect(67, AREA, .@t2);
immortal(.@t1); immortal(.@t2);
if (.@extra) {
.@x4=rand2(.@mx, .@x); .@x5=rand2(.@mx, .@x);
.@y4=rand2(.@my, .@y); .@y5=rand2(.@my, .@y);
.@t4=monster("006-10", .@x1, .@y1, "", EnergyBall, 1);
.@t5=monster("006-10", .@x2, .@y2, "", EnergyBall, 1);
specialeffect(67, AREA, .@t4);
specialeffect(67, AREA, .@t5);
immortal(.@t4); immortal(.@t5);
}
sleep(1500);
// This is just a prop
specialeffect(FX_LIGHTNING, AREA, .@t1);
specialeffect(FX_LIGHTNING, AREA, .@t2);
if (.@extra) {
specialeffect(FX_LIGHTNING, AREA, .@t4);
specialeffect(FX_LIGHTNING, AREA, .@t5);
}
sleep(500);
specialeffect(FX_CRITICAL, AREA, .@t1);
specialeffect(FX_CRITICAL, AREA, .@t2);
if (.@extra) {
specialeffect(FX_CRITICAL, AREA, .@t4);
specialeffect(FX_CRITICAL, AREA, .@t5);
}
areaharm(.@t1, 1, 450, HARM_MISC, Ele_Neutral, "filter_always", BL_PC|BL_MER|BL_HOM);
areaharm(.@t2, 1, 450, HARM_MISC, Ele_Neutral, "filter_always", BL_PC|BL_MER|BL_HOM);
if (.@extra) {
areaharm(.@t4, 1, 450, HARM_MISC, Ele_Neutral, "filter_always", BL_PC|BL_MER|BL_HOM);
areaharm(.@t5, 1, 450, HARM_MISC, Ele_Neutral, "filter_always", BL_PC|BL_MER|BL_HOM);
}
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);
if (.@extra) {
unitkill(.@t4); unitkill(.@t5);
}
break;
// (5/6) Weak AOE (~60s)
case 5:
case 11:
case 17:
specialeffect(60, AREA, .blanc);
sleep(500);
switch (rand2(3)) {
case 1:
unittalk(.blanc, "Cease! I shall ##Bpoison##b all of you!");
.@sc = (.@hp < 1 ? SC_DPOISON : SC_POISON);
break;
case 2:
unittalk(.blanc, "Cease! I shall ##Bhurt##b all of you!");
.@sc = SC_BLOODING;
break;
case 3:
unittalk(.blanc, "Cease! I shall ##Bsilence##b all of you!");
.@sc = SC_SILENCE;
break;
default:
unittalk(.blanc, "Cease! I shall ##Bcripple##b all of you!");
.@sc = SC_BLIND;
break;
}
areasc((.hard ? 7 : 5), 30000, .@sc, BL_PC|BL_HOM|BL_MER, 1, "filter_always", .blanc, 95000);
areaharm(.blanc, (.hard ? 7 : 5), 100, HARM_MAGI, Ele_Wind, "filter_always", BL_PC|BL_MER|BL_HOM);
// Forces everyone to sit, for cosmetic effect
areasc(18, 500, SC_BANANA_BOMB_SITDOWN_POSTDELAY, BL_PC|BL_HOM|BL_MER, 1, "filter_always", .blanc);
break;
}
// Strong AOE: Every 3 minutes (case = 0)
// Stalls all other skills for a while
if (.beats % 18 == 0) {
specialeffect(66, AREA, .blanc);
unittalk(.blanc, "I am Blanc, Halifax's captain!");
sleep((.hard ? 1000 : 1500));
specialeffect(700, AREA, .blanc);
unittalk(.blanc, "I'll never be caught by your ilk alive!");
sleep((.hard ? 1000 : 1500));
specialeffect(700, AREA, .blanc);
if (.@hp < 3) {
unittalk(.blanc, "Perish! ##BRage of Hungry Thunder##b!");
.@dmg=rand2(900, 1100);
areasc3((.hard ? 5 : 4), 11000, SC_STOMACHACHE, BL_PC|BL_HOM|BL_MER, (.hard ? 20 : 15), "filter_always", .blanc, 6000);
areasc(6, 10000, SC_BLIND, BL_PC|BL_HOM|BL_MER, 1, "filter_always", .blanc, 2000);
} else if (.@hp < 7) {
unittalk(.blanc, "Perish! ##BVoracious Thunder Storm##b!");
.@dmg=rand2(650, 900);
areasc3((.hard ? 4 : 3), 11000, SC_STOMACHACHE, BL_PC|BL_HOM|BL_MER, (.hard ? 15 : 10), "filter_always", .blanc, 4500);
} else {
unittalk(.blanc, "Perish! ##BEletric Needles##b!");
.@dmg=rand2(400, 650);
}
/* Three blocks */
specialeffect(66, AREA, .blanc);
areaharm(.blanc, (.hard ? 7 : 5), .@dmg, HARM_MAGI, Ele_Fire, "filter_always", BL_PC|BL_MER|BL_HOM);
sleep(500);
specialeffect(66, AREA, .blanc);
areaharm(.blanc, (.hard ? 15 : 10), .@dmg, HARM_MAGI, Ele_Holy, "filter_always", BL_PC|BL_MER|BL_HOM);
sleep(500);
specialeffect(66, AREA, .blanc);
specialeffect(FX_LIGHTNING, AREA, .blanc);
areaharm(.blanc, (.hard ? 24 : 18), .@dmg, HARM_MAGI, Ele_Wind, "filter_always", BL_PC|BL_MER|BL_HOM);
areasc((.hard ? 24 : 18), .@dmg*rand2(50, 100), SC_FROSTMISTY, BL_PC|BL_HOM|BL_MER, 1, "filter_always", .blanc, 10001 - (.@hp * 1000)); // SC_FROSTMISTY - General crippling
}
initnpctimer;
end;
OnInit:
.distance = 4;
.beats = 0; // Skill cooldown timer
.hard = 0; // Hard mode (for GMs)
.blanc = 0; // Boss GID
.state = 0; // Current platform ID
.maxhp = 0; // Blanc's max HP (getunitdata(.blanc, UDT_MAXHP) is broken)
// TODO: In general, we have no idea of platforms size
setarray $@BLANC_X, 53, 72, 71, 72, 52, 36, 34, 35, 35, 52;
setarray $@BLANC_Y, 63, 67, 53, 30, 30, 27, 48, 36, 63, 36;
// .@index = miller_rand(.@state, getcharid(0), 10);
end;
}
// Below be the warp system. Rightmost forwards, leftmost rewinds.
006-10,51,64,0 script #00610XX NPC_HIDDEN,1,0,{
end;
OnTouch:
// Now... WTF are we on miller rand?
if (!@miller_fix) {
for (.@i=0;.@i<10;.@i++) {
if (miller_rand(.@i, getcharid(0), 10) == 0) {
@miller_fix = .@i;
break;
}
}
}
// Set "@state" properly
@state = @miller_fix;
end;
}
006-10,57,61,0 script #00610WA+ NPC_FANCY_CIRCLE,0,0,{
end;
OnTouch:
// All portals are the same, anyway
if (compare(strnpcinfo(2), "+"))
@state = ((@state+1) % 10); // Technically, the loop is not needed
else
@state = ((@state-1) % 10); // Technically, this loop is not needed
.@index = miller_rand(@state, getcharid(0), 10);
slide $@BLANC_X[.@index], $@BLANC_Y[.@index];
end;
}
// NOTE: Vampire Bats are aggressive, they're tasked in disposing non-fighters
006-10,49,60,0 duplicate(#00610WA+) #00610WA- NPC_FANCY_CIRCLE,0,0
006-10,75,65,0 duplicate(#00610WA+) #00610WB+ NPC_FANCY_CIRCLE,0,0
006-10,68,64,0 duplicate(#00610WA+) #00610WB- NPC_FANCY_CIRCLE,0,0
006-10,75,50,0 duplicate(#00610WA+) #00610WC+ NPC_FANCY_CIRCLE,0,0
006-10,67,49,0 duplicate(#00610WA+) #00610WC- NPC_FANCY_CIRCLE,0,0
006-10,76,27,0 duplicate(#00610WA+) #00610WD+ NPC_FANCY_CIRCLE,0,0
006-10,68,26,0 duplicate(#00610WA+) #00610WD- NPC_FANCY_CIRCLE,0,0
006-10,56,27,0 duplicate(#00610WA+) #00610WE+ NPC_FANCY_CIRCLE,0,0
006-10,48,26,0 duplicate(#00610WA+) #00610WE- NPC_FANCY_CIRCLE,0,0
006-10,40,24,0 duplicate(#00610WA+) #00610WF+ NPC_FANCY_CIRCLE,0,0
006-10,32,24,0 duplicate(#00610WA+) #00610WF- NPC_FANCY_CIRCLE,0,0
006-10,37,51,0 duplicate(#00610WA+) #00610WG+ NPC_FANCY_CIRCLE,0,0
006-10,30,45,0 duplicate(#00610WA+) #00610WG- NPC_FANCY_CIRCLE,0,0
006-10,41,33,0 duplicate(#00610WA+) #00610WH+ NPC_FANCY_CIRCLE,0,0
006-10,30,33,0 duplicate(#00610WA+) #00610WH- NPC_FANCY_CIRCLE,0,0
006-10,44,63,0 duplicate(#00610WA+) #00610WI+ NPC_FANCY_CIRCLE,0,0
006-10,35,57,0 duplicate(#00610WA+) #00610WI- NPC_FANCY_CIRCLE,0,0
006-10,55,37,0 duplicate(#00610WA+) #00610WJ+ NPC_FANCY_CIRCLE,0,0
006-10,49,37,0 duplicate(#00610WA+) #00610WJ- NPC_FANCY_CIRCLE,0,0