summaryrefslogblamecommitdiff
path: root/npc/006-10/logic.txt
blob: 7241d8209fb527cdd14664272e1227202c839c82 (plain) (tree)
1
2
3
4
5
6
7
8
9



               
                                   

                           
                           
 
                                                                                 




















                                                                  
 


                                                                  
 




                                                       
 



















                                         
 




                                                                         
 





                                                       
 






                                                                                                        
 





                                                       
                                
                           
        
 



                                                                         
        

















                                                                     
 












                                                                         
 











                                                                                                                                 
 














                                                                 
 





                                                    
 































































































































































































                                                                                                                                                                                    
 
                 
        
 






                                                                            
 


                                                               
 
                                                        
        

 


                                                                  

        











                                                          
 
                                                              

        







                                                                           
 



















                                                                              
 
// 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