summaryrefslogblamecommitdiff
path: root/npc/functions/siege.txt
blob: 33375d314d49b503f7fef1db6b0aa299dc793c52 (plain) (tree)


















                                                                                                                                                      
                                 



                                                            

                                         





                           
                                                       
             
 
                                                       
                                     
                                       






                                                               
                                                 





                                               



                            
 
                                      



                
                                                                                           
 
                      
                         

                         



                            
                                                                                
                         
                                             


                                         
                                                       
 
                                                                        
                                                                                                          
                                          
     








                                                                                   
                                                                                


                                                  
                       


                                                              
                              
 
                                
                                                                       
                                                                     








                                     



                                          





                                                                         

















                                            
                                       














                                                        
                                                             
                                         
                      
                                         
                   
 
                        


                                                                                                                         
                                                                            

                    


                                                                                                  

                        
                                                                      
                                                                           





                                                                                      


           











                                                          
                                                                      
                                                                                  
               
                                 

                                     

                                                                 
                                                        



                                                                   
            
                                                                           
                             



           


                                         
                   
 

                                                               
                                                                      

                                                                  
                                                
 

                                           

                                
                                  
                                 



                         





                                                                    




                                                                           
                              

     
                                                      
                      

                                
                             

                                  
                             

                               
                             





                               
 




                                            


                         
                         
                                                                                                       

                                                                         

                                        
                                        

                                      


                     

                                                                               



                                                   
                                                   

                        


                                                                                                             
 
                                                                     


           









                                                           
                                              
                         

              

                                                      


                                                                      
                   


                                       
                                                          


                                       
                                                          


                                       
                                                          


           
 










                                                          



                        




                                                   
                              



                     
                                         


                                                                            





                                         
                                      
                                    
               

     


                                                                                 
                                         





                                                                     

     


                                             



                  
                                             
            
                                             



                          
                                              






                                                                                   

                                             


                 

                                             




                         
                        
















                                          
                                             

                  

                                                                                  
                                                                                      
                                             
              
             
                                                                                           















                                                    



                                            







                                                    
                    
                

                             
                                    
                                                                                    
                            

                                        
                                                  


                  

                             
                                     
                                                                                      
                            

                                        
                                                  

        
               

                             
                                      
                                                                                   
                            

                                        
                                                  

        
               
                             
                       
                                      
                                                                                   
                              

                                        
                                                  

        
               

                             
                                      
                                                                                   
                              

                                        
                                                  

        

 
// TMW2 scripts.
// Authors:
//    Jesusalva
// Description:
//    Town Siege utilities

// Siege Spawn
// Can be used anywhere to spawn on the whole map, margins respected.
// siege_spawn( map, mobID, Amount, eventID )
function	script	siege_spawn	{
    .@mp$=getarg(0);
    .@mid=getarg(1);
    .@qnt=getarg(2);
    .@ev$=getarg(3);

    areamonster(.@mp$, 20, 20, getmapinfo(MAPINFO_SIZE_X, .@mp$)-20, getmapinfo(MAPINFO_SIZE_Y, .@mp$)-20, strmobinfo(1, .@mid), .@mid, .@qnt, .@ev$);
    return;
}

// Calculate player average level
// flag 1 - Don't count dead players
// flag 2 - Return highest player level, instead of average.
// flag 4 - Return the total sum of levels instead.
// siege_calcdiff ( map{, flags} )
function	script	siege_calcdiff	{
    .@bsum=0;
    if (getarg(1,0) & 1)
        .@deadcount=true;
    if (getarg(1,0) & 2)
        .@onlyhighest=true;
    if (getarg(1,0) & 4)
        .@onlytotal=true;
    .@c = getunits(BL_PC, .@players, false, getarg(0));
    .@skip=0;

    // There is at least one player, do things properly
    for (.@i = 0; .@i < .@c; .@i++) {
        // Dead players are not counted
        if (.@deadcount) {
            if (ispcdead(strcharinfo(0, "", .@players[.@i]))) {
                .@skip+=1;
                continue;
            }
        }

        .@b=readparam(BaseLevel, .@players[.@i]);

        // GMs are not counted
        if (getgroupid(.@players[.@i]) >= 60) {
            .@skip+=1;
            .@b=0;
        }
        .@bsum+=.@b;
        if (.@b > .@highest)
            .@highest=.@b;
    }

    // Sanitize and fallback if needed
    .@c-=.@skip;
    if (!.@c)
        .@c=1;

    //debugmes "calcdiff: Total %d Average %d Highest %d", .@bsum, (.@bsum/.@c), .@highest;

    if (.@onlyhighest)
        return .@highest;
    else if (.@onlytotal)
        return .@bsum;
    else
        return (.@bsum/.@c);
}

// push to $@SIEGE_TMPMOBS the <mobID> if their level is within a x levels range
// above or below <level>
// siege_push ( mobID, {level{, variation}} )
function	script	siege_push	{
    .@mi=getarg(0);
    .@lv=getarg(1,0);
    .@var=getarg(2,14); // Old Default: 15, and then 10

    if ( is_between(.@lv-.@var, .@lv+(.@var/2), strmobinfo(3, .@mi)) ) {
        //debugmes "Monster %s (%d) level %d ~= %d", strmobinfo(1, .@mi), .@mi, strmobinfo(3, .@mi), .@lv;
        array_push($@SIEGE_TMPMOBS, .@mi);
    }
    return;
}

// Selects a monster based on player average strength and base difficulty
// It'll select monsters from 20 levels below to 20 levels above
// tp_mask is the same as TP_* constants and changes mobs present. Only TP_TULIM,
// TP_HURNS and TP_NIVAL are supported. You can use them with the "|" operand, eg.,
// TP_TULIM | TP_HURNS.
//
// It currently only creates $@SIEGE_TMPMOBS, you need to use any_of() manually.
// siege_selectmob ( blvl, difficulty{, tp_mask} )
function	script	siege_selectmob	{
    .@blv=getarg(0);
    .@dif=getarg(1, 0);
    .@tp=getarg(2, 0);

    // We don't need .@dif, so we convert difficulty to levels
    .@blv+=(.@dif/any(2,2,3));

    deletearray $@SIEGE_TMPMOBS;
    setarray $@SIEGE_TMPMOBS, ManaGhost, CandiedSlime, Bif, SlimeBlast;
    // Now we must select mobs, using array_push() to $@SIEGE_TMPMOBS
    // First, mobs on all envs
    siege_push(BlackScorpion, .@blv);
    siege_push(GreenSlime, .@blv);
    siege_push(CaveMaggot, .@blv);
    siege_push(MagicGoblin, .@blv);
    siege_push(AngryBat, .@blv);
    siege_push(BlackSlime, .@blv);
    siege_push(BlackScorpion, .@blv);
    siege_push(Forain, .@blv);
    siege_push(Terranite, .@blv);
    siege_push(JackO, .@blv);
    siege_push(BlackMamba, .@blv);
    siege_push(TerraniteProtector, .@blv);
    siege_push(Reaper, .@blv);

    // What if we are trying to select a boss and they're... overleveled?
    // We need a level 105, 120 and a level 135 monsters
    siege_push(FallenKing2, .@blv);
    siege_push(TerraniteKing, .@blv);

    // Now, mobs on only certain envs
    if (.@tp & TP_TULIM) {
        siege_push(AngryScorpion, .@blv);
        siege_push(AngryRedScorpion, .@blv);
        siege_push(DesertBandit, .@blv);
        siege_push(LavaSlime, .@blv);
        siege_push(OldSnake, .@blv);
        siege_push(Snake, .@blv);
    }
    if (.@tp & TP_HURNS) {
        siege_push(RedSlime, .@blv);
        siege_push(Bandit, .@blv);
        siege_push(RedMushroom, .@blv);
        siege_push(RobinBandit, .@blv);
        siege_push(AngryYellowSlime, .@blv);
        siege_push(GrassSnake, .@blv);
        siege_push(WickedMushroom, .@blv);
        siege_push(GreenDragon, .@blv);
    }
    if (.@tp & TP_NIVAL) {
        siege_push(Bluepar, .@blv);
        siege_push(Wolvern, .@blv);
        siege_push(Yeti, .@blv);
        siege_push(Moggun, .@blv); // PS. Not aggressive
    }

    // Removed
    //siege_push(DarkLizard, .@blv);
    //siege_push(Crafty, .@blv);

    return;
}

/////////////////////////////////////////////////////////////
// Prepare a siege with optional announce
// siege_setup ( map )
function	script	siege_setup	{
    .@m$=getarg(0);

    // Save old map zone
    if (getmapinfo(MAPINFO_ID, .@m$) < 1) {
        return Exception("SIEGE ERROR, INVALID MAP ID: "+.@m$, RB_ISFATAL|RB_DEBUGMES|RB_IRCBROADCAST|RB_GLOBALANNOUNCE);
    }
    //$@MZONE$[getmapinfo(MAPINFO_ID, .@m$)]=getmapinfo(MAPINFO_ZONE, .@m$);

    // Apply changes
    addmapmask .@m$, MASK_MATTACK;
    changemusic .@m$, any("mythica.ogg", "eric_matyas_ghouls.ogg", "misuse.ogg", "Arabesque.ogg");
    disablenpc("Mana Stone");
    if (.@m$ != "003-1")
        pvpon(.@m$);
    setmapflag(.@m$,mf_zone,"MMO"); // MMO Zone: Overrides GM Commands
    setmapflag(.@m$,mf_bexp,rand2(135,142)); // 35~42% EXP UP on siege maps
    .@tn$ = MapToLoc(.@m$, false);
    if (getd("$"+.@tn$+"_SIEGEXP")) {
        setmapflag(.@m$, mf_bexp, 300); // Triple EXP is on
        kamibroadcast("Experience for siege map has been set to 3×!", "INFORMATION");
        setd("$"+.@tn$+"_SIEGEXP", 0);
    }
    return;
}

// Check if boss was killed or not
// siege_check ( map )
function	script	siege_check	{
    .@m$=getarg(0);
    .@mb=0;

    .@mb+=mobcount(.@m$, "#SiegeCtrl::OnSergeantDeath");
    .@mb+=mobcount(.@m$, "#SiegeCtrl::OnLieutenantDeath");
    .@mb+=mobcount(.@m$, "#SiegeCtrl::OnCaptainDeath");
    .@mb+=mobcount(.@m$, "#SiegeCtrl::OnColonelDeath");
    .@mb+=mobcount(.@m$, "#SiegeCtrl::OnGeneralDeath");

    // Players failed, so reduce score in 1~5 (like Sergeant~General).
    // In future, it could be inverse proportion (-9 for sergeant, -1 for general)
    if (.@mb) {
        if ($GAME_STORYLINE == 2)
            $MK_TEMPVAR-=rand2(1, 5);

        kamibroadcast("Players failed to defend the city!!");
        debugmes "Number of boss grade monsters found: %d", .@mb;
        $SIEGE_DIFFICULTY=max(1, ($SIEGE_DIFFICULTY/2));
        // Lower the town exports in 5% (but never more than 25 GP)
        .@var$="$"+strtoupper(MapToLoc(.@m$))+"_EXPORT";
        .@pen=min(25, getd(.@var$)/20);
        setd(.@var$, getd(.@var$)-.@pen);
    } else {
        kamibroadcast("The city was defended with success! GG, everyone!");
        $SIEGE_DIFFICULTY+=1;
    }
    return;
}

// Revert what siege_setup did
// siege_revert ( map )
function	script	siege_revert	{
    .@m$=getarg(0);

    // Revert map zone (to town, or to blank) and delete backup
    removemapflag(.@m$,mf_zone);
    //setmapflag(.@m$,mf_zone,$@MZONE$[getmapinfo(MAPINFO_ID, .@m$)]);
    setmapflag(.@m$,mf_zone,"Normal2"); // Normal doesn't works...
    //setmapflag(.@m$,mf_zone,"All");
    //$@MZONE$[getmapinfo(MAPINFO_ID, .@m$)]="";

    removemapmask .@m$, MASK_MATTACK;
    changemusic .@m$, "caketown.ogg"; // :>
    pvpoff(.@m$);
    removemapflag(.@m$,mf_bexp);
    removemapflag(.@m$,mf_nosave);
    setmapflag(.@m$,mf_bexp,100);
    killmonsterall(.@m$);
    return;
}

// Create the Siege Boss for #SiegeCtrl utility, DO NEVER CAST TWICE
// siege_boss ( map, difficulty )
function	script	siege_boss	{
    .@m$=getarg(0);
    .@s=getarg(1,0);

    // We now select based on player average level and a seed of randomness
    .@val=siege_calcdiff(.@m$)+.@s;

    // Nobody is on map: Be TRULY random
    if (.@val < 10) {
        .@val=rand(20,97)+.@s;
    }

    // Switch an adequate boss, almost always stronger
    if (.@val <= 25) {
        .@mobId=MonsterSergeant;
        .@ts$="Sergeant";
    } else if (.@val <= 45) {
        .@mobId=MonsterLieutenant;
        .@ts$="Lieutenant";
    } else if (.@val <= 67) {
        .@mobId=MonsterCaptain;
        .@ts$="Captain";
    } else if (.@val <= 90) {
        .@mobId=MonsterColonel;
        .@ts$="Colonel";
    } else {
        .@mobId=MonsterGeneral;
        .@ts$="General";
    }

    // We want spawn point to be fixed
    .@lx=array_find($@LOCMASTER_MAP$, .@m$);
    .@xm=$@LOCMASTER_X[.@lx];
    .@ym=$@LOCMASTER_Y[.@lx];

    .@xm=.@xm+rand(-1,1);
    .@ym=.@ym+rand(-1,1);

    // Announce and spawn
    .@mg=monster(.@m$, .@xm, .@ym, strmobinfo(1, .@mobId), .@mobId, 1, "#SiegeCtrl::On"+.@ts$+"Death");

    // Boost the boss stats based on difficulty and nº of players online
    .@bhp=getunitdata(.@mg, UDT_MAXHP);
    .@bat=getunitdata(.@mg, UDT_ATKMAX);
    .@bai=getunitdata(.@mg, UDT_ATKMIN);
    .@bdf=getunitdata(.@mg, UDT_DEF);
    .@bcr=getunitdata(.@mg, UDT_CRIT);

    .@s+=getusers(1);

    setunitdata(.@mg, UDT_MAXHP,    .@bhp+(.@s*250)+.@val*5*getmapusers(.@m$));
    setunitdata(.@mg, UDT_HP,       .@bhp+(.@s*250)+.@val*5*getmapusers(.@m$));

    setunitdata(.@mg, UDT_ATKMAX,   .@bat+(.@s*5));
    setunitdata(.@mg, UDT_DEF,      .@bdf+(.@s*4));
    setunitdata(.@mg, UDT_CRIT,     .@bcr+(.@s*3));
    setunitdata(.@mg, UDT_ATKMIN,   .@bai+(.@s*2));

    // Spawn some scouts
    areamonster(.@m$, .@xm-1, .@ym-1, .@xm+1, .@ym+1, "Scout", any(GreenSlime,RedSlime,AngryYellowSlime), 2);
    areamonster(.@m$, .@xm-1, .@ym-1, .@xm+1, .@ym+1, "Scout", any(GreenSlime,RedSlime,AngryYellowSlime), 2);
    areamonster(.@m$, .@xm-1, .@ym-1, .@xm+1, .@ym+1, "Scout", any(GreenSlime,RedSlime,AngryYellowSlime), 2);

    announce("##1The Monster "+.@ts$+" arrived! Watch out!", bc_all);
    return;
}

// Spawn some monsters
// siege_cast ( map, NPCName, {, difficulty{, tpflag}} )
function	script	siege_cast	{
    // mz - map ; n - name ; d - difficulty ; tp - teleport
    // a - ammount ; e - mobId
    .@mz$=getarg(0);
    .@n$=getarg(1);
    .@d=getarg(2,0);
    .@tp=getarg(3,0);

    // Difficulty doesn't applies to Tulimshar
    if (.@mz$ == "003-1")
        .@d=0;

    siege_selectmob(siege_calcdiff(.@mz$), .@d, .@tp);

    // How many monsters? This value is multiplied by 3;
    // 1 per player (= 3 mobs), 1 each 5 difficulty, 1 always present.
    .@a=getmapusers(.@mz$);
    .@a+=(.@d/5)+1;

    .@e=any_of($@SIEGE_TMPMOBS);
    array_remove($@SIEGE_TMPMOBS, .@e);
    siege_spawn(.@mz$, .@e, .@a, "#SiegeCtrl::OnRespawn");

    .@e=any_of($@SIEGE_TMPMOBS);
    array_remove($@SIEGE_TMPMOBS, .@e);
    siege_spawn(.@mz$, .@e, .@a, "#SiegeCtrl::OnRespawn");

    .@e=any_of($@SIEGE_TMPMOBS);
    array_remove($@SIEGE_TMPMOBS, .@e);
    siege_spawn(.@mz$, .@e, .@a, "#SiegeCtrl::OnRespawn");
    return;
}


////////////////////////////////////////////
// Utility Function
// do_siege ( town, outskirts, varcode, flag, npc, timer )
function	script	do_siege	{
    .@m$=getarg(0);
    .@o$=getarg(1);
    .@c$=getarg(2);
    .@tp=getarg(3);
    .@n$=getarg(4);
    .@t=getarg(5);

    // Dry run
    if ($@SIEGE_ABORTED)
        return;

    // If no active player, KILL THE SCRIPT
    .@c = getunits(BL_PC, .@players, MAX_CYCLE_PC);
    .@idle = 0;
    for (.@i = 0; .@i < .@c; .@i++) {
        attachrid(.@players[.@i]);
        if (checkidle() < 450)
            .@idle++;
        detachrid();
    }

    // No one is active, cancel the event
    if (!.@idle) {
        kamibroadcast(col(b("EVENT CANCELLED DUE TO PLAYER INACTIVITY"),1));
        $@MK_AGGRO=$@MK_AGGRO/5; // Lower aggro bar to 20%

        // Cleanup and Garbage Collection
        siege_revert(.@m$);
        siege_revert(.@o$);
        enablenpc("Mana Stone");
        setd("$@SIEGE_"+.@c$, 0);
        setd("$@SIEGE_ABORTED", true);
        setd("$@MK_SCENE", MK_NONE);
        return;
    }

    // In past, we had a $@SIEGE_<town> to determine difficulty
    // This behavior is now deprecated, we use a global $SIEGE_DIFFICULTY
    // Which raises in 1 every victory and lowers in 1 every defeat (capped at 1)
    // But this behavior can be overriden
    if (!getd("$@SIEGE_"+.@c$)) {
        .@difc=max(1, getd("$SIEGE_DIFFICULTY"));

        // And then, we reset $@SIEGE_<town> so it can be manipulated
        // And set .@difc to this value
        setd("$@SIEGE_"+.@c$, .@difc);
    }

    // Set difficulty based on previous value
    .@difc=getd("$@SIEGE_"+.@c$);

    switch (.@t) {
    // Warmup
    case 70:
    case 95:
        siege_cast(.@m$, .@n$, .@difc, .@tp);
    case 35:
        siege_cast(.@o$, .@n$, .@difc, .@tp);
        break;
    // Setup and casts
    case 0:
        siege_setup(.@o$);
        siege_cast(.@o$, .name$, 0, TP_HURNS);
        break;
    case 60:
        siege_setup(.@m$);
        siege_cast(.@o$, .@n$, 0, .@tp);
        mapannounce(.@m$, "##2Message to all NPCs in town: Take shelter!", bc_map);
        break;
    case 90:
        siege_cast(.@m$, .@n$, .@difc, .@tp);
        siege_cast(.@o$, .@n$, .@difc, .@tp);
        break;
    // Boss stage
    case 280:
        siege_boss(.@m$, .@difc);
        siege_cast(.@m$, .@n$, .@difc, .@tp);
        break;
    // Difficulty Raisers
    case 310:
    case 520:
    case 640:
        .@varsig=.@difc;
        setd("$@SIEGE_"+.@c$, .@varsig+1);
    // Regular flow
    case 195:
    case 220:
    case 265:
    //case 280: BOSS WAVE
    //case 310: difficulty raiser
    case 355:
    case 400:
    case 445:
    case 490:
    //case 520: difficulty raiser
    case 535:
    case 580:
    case 625:
    //case 640: difficulty raiser
    case 670:
        siege_cast(.@m$, .@n$, .@difc, .@tp);
        break;
    // Ending flow
    // TODO: It would be better to make these values relative to MK_SIEGE_DURATION
    case 700:
        mapannounce(.@m$, "##1The Monster Army is planning to retreat soon!", bc_map);
        siege_cast(.@m$, .@n$, .@difc, .@tp);
        break;
    case 760:
        mapannounce(.@m$, "##1The Monster Army is withdrawing within 30 seconds!", bc_map);
        $@MK_SCENE=MK_NONE;
        $@MK_AGGRO=$@MK_AGGRO/20;
        break;
    // Check db/constants.conf to change this value!
    case MK_SIEGE_DURATION:
        siege_check(.@m$);
        siege_revert(.@m$);
        siege_revert(.@o$);
        enablenpc("Mana Stone");
        setd("$@SIEGE_"+.@c$, 0);
        break;
    }

    return;
}

// Utility NPC
-	script	#SiegeCtrl	NPC_HIDDEN,{
    end;

OnRespawn:
    if (playerattached()) {
        getmapxy(.@m$,.@x,.@y,0);
        if (rand(10000) <= $coinsrate)
            makeitem StrangeCoin, 1, .@m$, .@x, .@y;
    }
    end;

// Boss Death Labels
OnSergeantDeath:
    if ($GAME_STORYLINE == 2)
        $MK_TEMPVAR+=1;
    getitem StrangeCoin, rand2(1,5);
    announce("##2The Monster Sergeant was defeated by "+strcharinfo(0)+"!", bc_all);
    $@EXP_EVENT=rand2(1, 3);
    $@EXP_EVENT_TIME=1;
    donpcevent "@exprate::OnPlayerCall";
    specialeffect(FX_FANFARE, AREA, getcharid(3));
    end;

OnLieutenantDeath:
    if ($GAME_STORYLINE == 2)
        $MK_TEMPVAR+=3;
    getitem StrangeCoin, rand2(5,10);
    announce("##2The Monster Lieutenant was defeated by "+strcharinfo(0)+"!", bc_all);
    $@EXP_EVENT=rand2(4, 6);
    $@EXP_EVENT_TIME=1;
    donpcevent "@exprate::OnPlayerCall";
    specialeffect(FX_FANFARE, AREA, getcharid(3));
    end;

OnCaptainDeath:
    if ($GAME_STORYLINE == 2)
        $MK_TEMPVAR+=5;
    getitem StrangeCoin, rand2(10,15);
    announce("##2The Monster Captain was defeated by "+strcharinfo(0)+"!", bc_all);
    $@EXP_EVENT=rand2(7, 9);
    $@EXP_EVENT_TIME=1;
    donpcevent "@exprate::OnPlayerCall";
    specialeffect(FX_FANFARE, AREA, getcharid(3));
    end;

OnColonelDeath:
    if ($GAME_STORYLINE == 2)
        $MK_TEMPVAR+=7;
    getitem StrangeCoin, rand2(15,20);
    announce("##2The Monster Colonel was defeated by "+strcharinfo(0)+"!", bc_all);
    $@EXP_EVENT=rand2(10, 12);
    $@EXP_EVENT_TIME=1;
    donpcevent "@exprate::OnPlayerCall";
    specialeffect(FX_FANFARE, AREA, getcharid(3));
    end;

OnGeneralDeath:
    if ($GAME_STORYLINE == 2)
        $MK_TEMPVAR+=9;
    getitem StrangeCoin, rand2(20,25);
    announce("##2The Monster General was defeated by "+strcharinfo(0)+"!", bc_all);
    $@EXP_EVENT=rand2(13, 15);
    $@EXP_EVENT_TIME=1;
    donpcevent "@exprate::OnPlayerCall";
    specialeffect(FX_FANFARE, AREA, getcharid(3));
    end;

}