EQEmulator Forums

EQEmulator Forums (https://www.eqemulator.org/forums/index.php)
-   Quests::Completed (https://www.eqemulator.org/forums/forumdisplay.php?f=633)
-   -   Dynamic instancing by level range (https://www.eqemulator.org/forums/showthread.php?t=37101)

jshows1 07-26-2013 10:26 AM

Dynamic instancing by level range
 
This community has been overwhelmingly helpful in assisting me with getting my server up and running. When I first started I had a plethora of questions (will have more I'm sure!) But you guys have been patient and extremely positive.

Now it's time for me to give back to the community. A friend and I have been trying to find a way to create instances that would change level ranges based on the quest requestor's level. Yes this is similar to what LDON's do. With this PL file, however, you canmodify it to make instances of any zone.

In my example below, we've used Crushbone. With this, all you would have to do is populate each version of the instance with the appropriate level of mobs. This, of course, can be any range you want. You determine.

I apologize if this is not as neat as some PL files. We've just recently begun to learn how PL files work and most of the ones we've made have been simply trial nad error to see what works. So it may not be as neat as some, but it works perfectly.

Code:

sub EVENT_SAY {
        my $EnterInst = quest::saylink("Enter the Instance", 2);
        my $Destroy = quest::saylink("destroy", 2);
    my $worse = quest::saylink("worse", 2);
        my $evil = quest::saylink("evil", 2);
        my $InstId = quest::GetInstanceID("crushbone", $version);

        if($text=~/hail/i){
        quest::say("Thank Tunare you're here, $name!  Something dreadful is taking place inside Castle Crushbone.  Yes, yes, I realize that the whole place is dreadful to begin with.  But it's much [$worse] now.");
        }
        if($text=~/worse/i){
        quest::say("Some new evil has moved in and set up shop, destroying everything inside.  Where the Crushbone Orcs were destructive and chaotic, this new [$evil] is much more of a threat to all of Tunare's creatures.");
        }
        if($text=~/evil/i){
        quest::say("if you are willing to assist us, I will need to ensure your safe passage.  The new evil inside Crushbone has not yet reached our dimension.  However, he has infilftrated alternate diminsions.  If we can defeat him in these other planes of existence, perhaps we can prevent him from finding our current diminsion.  Just say when you are ready and I can open a portal enabling you to [$EnterInst].  Also, if you are done fighting, or wish to start over, I can [$Destroy] any old instances that may be lingering. ");
        }
        if ($text =~/Enter the Instance/i) {
                my $j;
                for($j=1; $j<=6; $j++) {
                        my $version = $j;
                        my $InstId = quest::GetInstanceID("crushbone", $version);
                if ($InstId > 0) {
                quest::MovePCInstance(58, $InstId, 166, -586, 3.13);
                }
                elsif ((quest::GetInstanceID("crushbone", $ulevel + 1)) > 0) {
                quest::MovePCInstance(58, quest::GetInstanceID("crushbone", $ulevel + 1), 166, -586, 3.13);
                }
                elsif ((quest::GetInstanceID("crushbone", $ulevel - 1)) > 0) {
                quest::MovePCInstance(58, quest::GetInstanceID("crushbone", $ulevel - 1), 166, -586, 3.13);
                }
                elsif (($ulevel >= (($version * 10) - 9)) && ($ulevel <= ($version * 10))) {
                my $i_id = quest::CreateInstance("crushbone", $version, 21600);
        quest::AssignGroupToInstance($i_id);
                quest::AssignToInstance($i_id);
                quest::MovePCInstance(58, $i_id, 166, -586, 3.13);
                }
                else {
                }
                }
        }
        if($text=~/destroy/i){
                my $i;
                for($i=0; $i<=6; $i++) {
                        quest::DestroyInstance(quest::GetInstanceID("crushbone", $i));
                }
        }
}

We also had to find a way to have the player zone back out of the instance. For this, there may be a better way, but we created an NPC that acts like a zone line. Below is his PL file. Basically we put an NPC where we want the zone line, changed him to an invisible man, made his name ___ so it does not show up, and simply made him untargetable and made it to where players can't aggro or damage him. Again, there may be an easier way, but this works perfectly for us.

Code:

sub EVENT_SPAWN {
        $x = $npc->GetX();
        $y = $npc->GetY();
        quest::set_proximity($x - 70, $x + 70, $y - 70, $y + 70);
}

sub EVENT_ENTER {
        $client->Message(315, "Greater Faydark");
        quest::movepc(54,-56.33,2552.77,18.97,125);
}

sub EVENT_EXIT {
        quest::set_proximity($x - 70, $x + 70, $y - 70, $y + 70);
}

If anyone has any questions, I'd be more than happy to answer them. Again, thank you guys kindly for helping me get started. I hope this can help even a few people.

rhyotte 07-26-2013 12:34 PM

Nice.... I can see this getting some good use.

Secrets 07-26-2013 04:51 PM

A better idea would to be to take the instance ID, have a central zone controller, and scale NPCs in the zone based on level range in a perl script upon NPC spawn with signals and/or entity variables.

You can accomplish this with the modifynpcstat perl command and Akkadius' zone copying tool.

jshows1 07-27-2013 12:29 AM

Quote:

Originally Posted by Secrets (Post 222743)
A better idea would to be to take the instance ID, have a central zone controller, and scale NPCs in the zone based on level range in a perl script upon NPC spawn with signals and/or entity variables.

You can accomplish this with the modifynpcstat perl command and Akkadius' zone copying tool.

This sounds like great information. Thanks a bunch, Secrets. I'll definitely look into this method and learn how to implement what you're talking about. Certainly sounds more efficient than what we did. Again, we're just learning this Pearl stuff, so we're still a little clunky lol.

And thanks a bunch for the encouragement, Rhyotte!

Akkadius 07-27-2013 01:18 AM

Quote:

Originally Posted by jshows1 (Post 222756)
This sounds like great information. Thanks a bunch, Secrets. I'll definitely look into this method and learn how to implement what you're talking about. Certainly sounds more efficient than what we did. Again, we're just learning this Pearl stuff, so we're still a little clunky lol.

And thanks a bunch for the encouragement, Rhyotte!

Secrets pretty much ripped this idea from what I had implemented and talked about. I've had it on my server for over 8 months, however I haven't had a real content push in a long time YET.

You have a NPC that iterates through the entity list and modify's their stats on the fly based on criteria you specify.

In my case I do really crafty and flexible loading from a database, and this NPC exists in every zone and the NPC's are scaled based on two keys:
  • Level
  • Type (Trash, Named, Raid)

Here is my live and working zone controller if you want to try and spin an idea of how I do this.

Code:

### Akkadius
### Zonecontroller
### This Entity sits in every zone ready to accept signals to handle various requests
### First use of this will be to handle automatic scaling of the zone if desired

### Insert Zone Controller into database
### SQL: (Assumes NPC ID 50)
### INSERT INTO `npc_types` (`id`, `name`, `lastname`, `level`, `race`, `class`, `bodytype`, `hp`, `mana`, `gender`, `texture`, `helmtexture`, `size`, `hp_regen_rate`, `mana_regen_rate`, `loottable_id`, `merchant_id`, `alt_currency_id`, `npc_spells_id`, `npc_faction_id`, `adventure_template_id`, `trap_template`, `mindmg`, `maxdmg`, `attack_count`, `npcspecialattks`, `aggroradius`, `face`, `luclin_hairstyle`, `luclin_haircolor`, `luclin_eyecolor`, `luclin_eyecolor2`, `luclin_beardcolor`, `luclin_beard`, `drakkin_heritage`, `drakkin_tattoo`, `drakkin_details`, `armortint_id`, `armortint_red`, `armortint_green`, `armortint_blue`, `d_meele_texture1`, `d_meele_texture2`, `prim_melee_type`, `sec_melee_type`, `runspeed`, `MR`, `CR`, `DR`, `FR`, `PR`, `Corrup`, `see_invis`, `see_invis_undead`, `qglobal`, `AC`, `npc_aggro`, `spawn_limit`, `attack_speed`, `findable`, `STR`, `STA`, `DEX`, `AGI`, `_INT`, `WIS`, `CHA`, `see_hide`, `see_improved_hide`, `trackable`, `isbot`, `exclude`, `ATK`, `Accuracy`, `slow_mitigation`, `version`, `maxlevel`, `scalerate`, `private_corpse`, `unique_spawn_by_name`, `underwater`, `isquest`, `emoteid`) VALUES (50, 'zonecontroller', NULL, 1, 240, 1, 11, 31, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 'ZiGH', 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 28, 28, 1.25, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 75, 75, 75, 75, 80, 75, 75, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 100, 0, 0, 0, 0, 0);

sub EVENT_SPAWN{
        $npc->TempName("");
        #$Debug = " ";
        quest::gmsay("[Zonecontroller] [ALIVE] Zone: $zonesn Instance Version: $instanceversion InstID $instanceversion", 15);
       
        #LoadPetTable(); ### Load Pet Table into Memory for referencing
        #LoadPetScaleData(); ### Load Pet Scaling Table into Memory
       
        LoadStaticNPCScaling($zonesn); ### Load NPC Static Scaling
        LoadStaticZoneScaling(); ### Load Zone Static Scaling
       
        quest::settimer("scaleall", 3); ### Run Scaling routine
        $ScalingLoaded = "";
       
        # %DreamZones = plugin::LoadDreamZoneList();
        # $n = 1;
        # while($DreamZones{$n}[0]){
        #        if($instanceversion == 1 && $DreamZones{$n}[0] eq $zonesn){
        #                if(!$entity_list->GetNPCByNPCTypeID(1006398)){
        #                        $query = "SELECT zone, x, y, z, id FROM `zone_points` WHERE `zone` = '". $zonesn ." AND `version` = " . $instanceversion . "";
        #                        $connect = plugin::LoadMysql();
        #                        quest::shout("loading zone points for dream zones");
        #                        $query_handle = $connect->prepare($query); $query_handle->execute();
        #                        while(@row = $query_handle->fetchrow_array()){
        #                                quest::spawn2(1006398, 0, 0, int($row[1]), int($row[2]), int($row[3]), 0);
        #                        }
        #                }
        #        }
        #        $n++;
        # }
}

sub EVENT_TIMER{
        if($timer eq "scaleall"){ ScaleProcedure(); quest::stoptimer("scaleall"); quest::gmsay("Scaling routine complete", 15);  }
        if($timer eq "levelset"){
                @ent = $entity_list->GetNPCList();
                $Level = $npc->GetEntityVariable("queuesetlevel2");
                $Variance = $npc->GetEntityVariable("queuesetlevel3");
                foreach $NPC (@ent){
                        if($NPC->GetNPCTypeID() > 1000){
                                if($Variance > 0){ } else{ $Variance = 0; }
                                $NPC->SetLevel($Level + (RandomRange(0 - $Variance, $Variance)));
                        }
                }
                $npc->SetEntityVariable("queuesetlevel", 0);
                $npc->SetEntityVariable("queuesetlevel2", 0);
                $npc->SetEntityVariable("queuesetlevel3", 0);
                quest::gmsay("Level Scaling Done...", 15);
                quest::stoptimer("levelset");
        }
}

sub EVENT_SIGNAL{
        if($signal == 10){ ScaleProcedure(); }
        if($signal == 11){ $npc->SetEntityVariable("queuesetlevel", 1); return; }
        if($npc->GetEntityVariable("queuesetlevel") > 0 && $npc->GetEntityVariable("queuesetlevel2") == 0){ $npc->SetEntityVariable("queuesetlevel2", $signal); quest::settimer("levelset", 1); }
        if($npc->GetEntityVariable("queuesetlevel2") > 0){ $npc->SetEntityVariable("queuesetlevel3", $signal); }
        ### Getting Signal from Spawned NPC ###
        if($signal == 21){ $npc->SetEntityVariable("queuesetnpc", 1);  return; }
        if($npc->GetEntityVariable("queuesetnpc") eq "1" && $ScalingLoaded == 3){ $npc->SetEntityVariable("queuesetnpc", 0); ScaleProcedure($signal); }
        if($signal == 23){ $Debug = 1; }
}

sub ScaleProcedure{
        ### Read from DB
        if(!$SD[1][0][0]){ LoadScaling(); } ### Scaling Vars Empty, reload them from DB
        $NPC = 0; $pop = "";
       
        if($_[0]){
                @ent = $entity_list->GetNPCByNPCTypeID($_[0]);
                $pop = " POP";
                # quest::gmsay("Scaling NPC ID " . $_[0], 15);
        }
        else{ @ent = $entity_list->GetNPCList(); $ScalingLoaded = 2; }
        foreach $NPC (@ent){
                ### Pet Scaling Handler
                my $IsPet = $PetD[$NPC->GetNPCTypeID()][0]; if(!$IsPet){ $IsPet = 0; }
                if(($NPC->GetOwnerID() != 0 || $IsPet > 0) && ($entity_list->GetClientByName("AkkaDark") || $entity_list->GetClientByName("Akkamage") || $entity_list->GetClientByName("Akkasham") || $entity_list->GetClientByName("Akkabeast"))){
                        if($PetD[$NPC->GetNPCTypeID()][0] > 0 && $PetD[$NPC->GetNPCTypeID()][3] == 1){
                                ###Swarm Pet
                                #quest::shout("A Swarm Pet has been casted... Processing code...");
                                my $PetLevel = $NPC->GetLevel();
                                $NPC->ModifyNPCStat("max_hp", $PetSD[$PetLevel][1]);
                                $NPC->SetHP($NPC->GetMaxHP());
                                $NPC->ModifyNPCStat("max_mana", $PetSD[$PetLevel][2]);
                                $NPC->ModifyNPCStat("min_hit", $PetSD[$PetLevel][3]);
                                $NPC->ModifyNPCStat("max_hit", $PetSD[$PetLevel][4]);
                        }else{
                                my $PetOwnerID = $entity_list->GetClientByID($NPC->GetOwnerID());
                                if($PetOwnerID){ $PetOwner = $PetOwnerID->GetCleanName(); }
                                my $PetOwnerEnt = $entity_list->GetClientByID($NPC->GetOwnerID());
                                my $PetName = $NPC->GetCleanName();
                                my $PetLevel = $PetOwnerEnt->GetEntityVariable("originlevel");
                                #my $PetEntID = $PetOwnerEnt->SetEntityVariable($NPC);
                                $NPC->SetLevel($PetOwnerEnt->GetLevel());
                                my $PetPower = $PetOwnerEnt->GetEntityVariable("petpower") / 100 + 1; if($PetPower > 0){}else{ $PetPower = 1; }
                                if($Debug){ quest::gmsay("[Zonecontroller]: Found pet to scale - Owner " . $PetOwner . " Pet: " . $PetName, 15); }
                                $NPC->ModifyNPCStat("max_hp", $PetSD[$PetLevel][1] + ($PetOwnerEnt->GetINT() * 10) * $PetPower);
                                if($Debug){ quest::gmsay("[Zonecontroller]: Pet: " . $PetName . " max_hp: " . $PetSD[$PetLevel][1] . " INT mod " . ($PetOwnerEnt->GetINT() * 10) . " Pet Power: " . $PetPower ." Final " . ($PetSD[$PetLevel][1] + ($PetOwnerEnt->GetINT() * 10) * $PetPower), 15); }
                                $NPC->SetHP($NPC->GetMaxHP());
                                $NPC->ModifyNPCStat("max_mana", $PetSD[$PetLevel][2]);
                                $NPC->ModifyNPCStat("min_hit", $PetSD[$PetLevel][3]);
                                $NPC->ModifyNPCStat("max_hit", $PetSD[$PetLevel][4] + ($PetOwnerEnt->GetCHA() * 5) * ($PetPower / 2));
                                if($Debug){ quest::gmsay("[Zonecontroller]: Pet: " . $PetName . " max_hit: " . $PetSD[$PetLevel][4] . " CHA mod " . ($PetOwnerEnt->GetCHA() * 5) . " Pet Power: " . ($PetPower / 2) ." Final " . ($PetSD[$PetLevel][4] + ($PetOwnerEnt->GetCHA() * 5) * ($PetPower / 2)), 15); }
                                $NPC->ModifyNPCStat("attack_speed", $PetSD[$PetLevel][5]);
                                $NPC->ModifyNPCStat("ac",  $PetSD[$PetLevel][6]);
                                $NPC->ModifyNPCStat("hp_regen", $PetSD[$PetLevel][7]);
                                $NPC->ModifyNPCStat("spellscale", $PetSD[$PetLevel][8]);
                                $NPC->ModifyNPCStat("healscale", $PetSD[$PetLevel][9]);
                                $NPC->ModifyNPCStat("special_attacks", $PetSD[$PetLevel][10]);
                                $NPC->ModifyNPCStat("accuracy", 50);
                                #quest::shout($PetPower);
                        }
                }
       
                ### Hide Invis NPC Names
                if(($NPC->GetRace() == 127 || $NPC->GetRace() == 240) && $NPC->GetLevel() < 100 && $NPC->GetNPCTypeID() > 1000){ if($NPC->GetCleanName()=~/shadowed/i){} else{ $NPC->TempName(""); $NPC->ModifyNPCStat("special_attacks", "AZ"); } }
               
                if($NPC->GetEntityVariable("Scaled") != 1){
                        #Static NPC Scaling
                        $n_name = $NPC->GetCleanName(); $n_name =~ tr/ /_/;
                        if($N_SD{$n_name}[0]){  ScaleNPCStatic($NPC, $n_name);  }
                }
                ### SCALING
                #$NPC->Say("Scaling...");
                if($NPC->GetPetSpellID() == 0 && $NPC->GetRace() != 127 && $NPC->GetRace() != 240 && $NPC->GetClass() < 20 && $NPC->GetNPCTypeID() > 1000 && $NPC->GetEntityVariable("Scaled") != 1){
                       
                        $NTYPE = 0; $NN = "NPC";
                        if(substr($NPC->GetName(), 0, 1) eq "#" && substr($NPC->GetName(), 1, 2) ne "#"){ $NTYPE = 1; $NN = "Named"; }
                        if(substr($NPC->GetName(), 0, 2) eq "##" && substr($NPC->GetName(), 2, 3) ne "#"){ $NTYPE = 2; $NN = "Raid"; }
                       
                        $NPC->ModifyNPCStat("max_hp", $SD[$NPC->GetLevel()][$NTYPE][2] * $SZD{$zonesn}[$instanceversion][$NTYPE][2]);
                        $NPC->ModifyNPCStat("max_mana", $SD[$NPC->GetLevel()][$NTYPE][3]  * $SZD{$zonesn}[$instanceversion][$NTYPE][3]);
                        $NPC->ModifyNPCStat("min_hit", $SD[$NPC->GetLevel()][$NTYPE][4] *  $SZD{$zonesn}[$instanceversion][$NTYPE][4]);
                        $NPC->ModifyNPCStat("max_hit", $SD[$NPC->GetLevel()][$NTYPE][5] *  $SZD{$zonesn}[$instanceversion][$NTYPE][5]);
                        $NPC->ModifyNPCStat("attack_speed", $SD[$NPC->GetLevel()][$NTYPE][6] *  $SZD{$zonesn}[$instanceversion][$NTYPE][6]);
                        $NPC->ModifyNPCStat("ac", $SD[$NPC->GetLevel()][$NTYPE][7] *  $SZD{$zonesn}[$instanceversion][$NTYPE][7]);
                        $NPC->ModifyNPCStat("str", $SD[$NPC->GetLevel()][$NTYPE][8]);
                        $NPC->ModifyNPCStat("sta", $SD[$NPC->GetLevel()][$NTYPE][9]);
                        $NPC->ModifyNPCStat("dex", $SD[$NPC->GetLevel()][$NTYPE][10]);
                        $NPC->ModifyNPCStat("agi", $SD[$NPC->GetLevel()][$NTYPE][11]);
                        $NPC->ModifyNPCStat("int", $SD[$NPC->GetLevel()][$NTYPE][12]);
                        $NPC->ModifyNPCStat("wis", $SD[$NPC->GetLevel()][$NTYPE][13]);
                        $NPC->ModifyNPCStat("cha", $SD[$NPC->GetLevel()][$NTYPE][14]);
                        $NPC->ModifyNPCStat("mr", $SD[$NPC->GetLevel()][$NTYPE][15]);
                        $NPC->ModifyNPCStat("cr", $SD[$NPC->GetLevel()][$NTYPE][16]);
                        $NPC->ModifyNPCStat("dr", $SD[$NPC->GetLevel()][$NTYPE][17]);
                        $NPC->ModifyNPCStat("fr", $SD[$NPC->GetLevel()][$NTYPE][18]);
                        $NPC->ModifyNPCStat("pr", $SD[$NPC->GetLevel()][$NTYPE][19]);
                       
                       
                        ### Is there a static zone entry?
                        if($SZD{$zonesn}[$instanceversion][$NTYPE][11] != 1){
                                $NPC->ModifyNPCStat("special_attacks", $SZD{$zonesn}[$instanceversion][$NTYPE][11]);
                        }else{
                                $NPC->ModifyNPCStat("special_attacks", $SD[$NPC->GetLevel()][$NTYPE][21]);
                        }
                        $NPC->ModifyNPCStat("hp_regen", $SD[$NPC->GetLevel()][$NTYPE][20]  *  $SZD{$zonesn}[$instanceversion][$NTYPE][10]);
                        $NPC->SetEntityVariable("hpregen", $SD[$NPC->GetLevel()][$NTYPE][20]  *  $SZD{$zonesn}[$instanceversion][$NTYPE][10]);
                        $NPC->ModifyNPCStat("spellscale", $SD[$NPC->GetLevel()][$NTYPE][22]  *  $SZD{$zonesn}[$instanceversion][$NTYPE][9]);
                        $NPC->ModifyNPCStat("healscale", $SD[$NPC->GetLevel()][$NTYPE][23]  *  $SZD{$zonesn}[$instanceversion][$NTYPE][8]);
                       
                        $LID = (200000 + ($NTYPE * 1000) + $NPC->GetLevel());
                        if($NPC->GetLoottableID() != $LID){
                                $NPC->ModifyNPCStat("loottable_id", (210000 + ($NTYPE * 1000) + $NPC->GetLevel())); $NPC->AddLootTable();
                                $NPC->ModifyNPCStat("loottable_id", (200000 + ($NTYPE * 1000) + $NPC->GetLevel())); $NPC->AddLootTable(); 
                        }
                        $NPC->SetHP($NPC->GetMaxHP());
                        $NPC->SetEntityVariable("Scaled", 1); if($Debug){ $NPC->SetEntityVariable("ScaledType", 1); }
                       
                        if($Debug){
                                quest::gmsay("Scaling $pop $NN [" . $NPC->GetCleanName() . "]
                                        HP:[". $SD[$NPC->GetLevel()][$NTYPE][2] * $SZD{$zonesn}[$instanceversion][$NTYPE][2] . "]
                                        hp_regen:[". ($SD[$NPC->GetLevel()][$NTYPE][20]  *  $SZD{$zonesn}[$instanceversion][$NTYPE][10]) . "]
                                        min_hit:[". ($SD[$NPC->GetLevel()][$NTYPE][4] *  $SZD{$zonesn}[$instanceversion][$NTYPE][4]) . "]
                                        max_hit:[".( $SD[$NPC->GetLevel()][$NTYPE][5] *  $SZD{$zonesn}[$instanceversion][$NTYPE][5]) . "]
                                        spec:[". $SD[$NPC->GetLevel()][$NTYPE][21] . "]
                                        NTYPE:[". $NTYPE . "]
                                        ", 15);
                        }
                       
                        if(!$SD[$NPC->GetLevel()][$NTYPE][0]){ quest::gmsay($NPC->GetCleanName() . ": ERROR! Missing Scaling Entry! For level " . $NPC->GetLevel() . " Type: $NTYPE", 13); }
                }
                # Level 255 NPC's are considered quest NPC's and shall not be attacked #
                if($NPC->GetLevel() == 255){ $NPC->ModifyNPCStat("special_attacks", "AHGZ"); }
        }
        if($ScalingLoaded == 2){ $ScalingLoaded = 3; }
}

sub LoadScaling{
        $connect = plugin::LoadMysql();
        $query = "SELECT
                cust_npc_scaling.level,
                cust_npc_scaling.type,
                cust_npc_scaling.hp,
                cust_npc_scaling.mana,
                cust_npc_scaling.mindmg,
                cust_npc_scaling.maxdmg,
                cust_npc_scaling.attack_speed,
                cust_npc_scaling.AC,
                cust_npc_scaling.STR,
                cust_npc_scaling.STA,
                cust_npc_scaling.DEX,
                cust_npc_scaling.AGI,
                cust_npc_scaling._INT,
                cust_npc_scaling.WIS,
                cust_npc_scaling.CHA,
                cust_npc_scaling.MR,
                cust_npc_scaling.CR,
                cust_npc_scaling.DR,
                cust_npc_scaling.FR,
                cust_npc_scaling.PR,
                cust_npc_scaling.hp_regen,
                cust_npc_scaling.npcspecialattks,
                cust_npc_scaling.spellscale,
                cust_npc_scaling.healscale
                FROM
                cust_npc_scaling
                ORDER BY cust_npc_scaling.level, cust_npc_scaling.type";
        $query_handle = $connect->prepare($query);
        $query_handle->execute();
        $NTYPE = 0;
        while (@row = $query_handle->fetchrow_array()){ $SD[$row[0]][$row[1]] = [@row]; }
}

sub LoadStaticZoneScaling{
        ### Load Static Zone Scaling Data ###
        $connect = plugin::LoadMysql();
        ### Default empty values to 1
        for($i = 2; $i < 17; $i++){
                for($t = 0; $t < 3; $t++){
                        if(!$SZD{$zonesn}[$instanceversion][$t][$i] || $SZD{$zonesn}[$instanceversion][$t][$i] == 0){
                                $SZD{$zonesn}[$instanceversion][$t][$i] = 1;
                        }
                }
        }
       
        $query = "SELECT `zonesn`, `version`, `hp`, `mana`, `mindmg`, `maxdmg`, `attack_speed`, `ac`, `healscale`, `spellscale`, `hpregen`, `specialattacks`, `type` FROM `cust_npc_zonescale_static` WHERE `zonesn` = '" . $zonesn . "'"; $query_handle = $connect->prepare($query); $query_handle->execute(); my @SZD;
        while (@row = $query_handle->fetchrow_array()){
                $SZD{$row[0]}[$row[1]][$row[12]] = [@row]; 
                if($SZD{$zonesn}[$instanceversion][0]){
                        $Mod = $SZD{$zonesn}[$instanceversion][2]; $npc->SetEntityVariable("ScaleMod", $Mod);
                        quest::gmsay("[Zonecontroller] (Static Data) [Type] : " . $row[12] . " Zone Data: HP=" . ($SZD{$zonesn}[$instanceversion][$row[12]][2] * 100) . "(%%) Mana=" . ($SZD{$zonesn}[$instanceversion][$row[12]][3] * 100) . "(%%) MINDMG=" . ($SZD{$zonesn}[$instanceversion][$row[12]][4] * 100) . "(%%) MAXDMG=" . ($SZD{$zonesn}[$instanceversion][$row[12]][5] * 100) . "(%%) ATK_SPD=" . ($SZD{$zonesn}[$instanceversion][$row[12]][6] * 100) . "(%%) AC=" . ($SZD{$zonesn}[$instanceversion][$row[12]][7] * 100) . "(%%) SPELLSCALE=" . ($SZD{$zonesn}[$instanceversion][$row[12]][8] * 100) . "(%%) HEALSCALE=" . ($SZD{$zonesn}[$instanceversion][$row[12]][9] * 100) . "(%%) HPREGEN=" . ($SZD{$zonesn}[$instanceversion][$row[12]][10] * 100) . "(%%)", 15);
                       
                }
        }
}

sub LoadStaticNPCScaling{
        $connect = plugin::LoadMysql();
        $query = "SELECT
                cust_npc_scale_static.`name`,
                cust_npc_scale_static.zonesn,
                cust_npc_scale_static.hp,
                cust_npc_scale_static.mana,
                cust_npc_scale_static.mindmg,
                cust_npc_scale_static.maxdmg,
                cust_npc_scale_static.attack_speed,
                cust_npc_scale_static.AC,
                cust_npc_scale_static.STR,
                cust_npc_scale_static.STA,
                cust_npc_scale_static.DEX,
                cust_npc_scale_static.AGI,
                cust_npc_scale_static._INT,
                cust_npc_scale_static.WIS,
                cust_npc_scale_static.CHA,
                cust_npc_scale_static.MR,
                cust_npc_scale_static.CR,
                cust_npc_scale_static.DR,
                cust_npc_scale_static.FR,
                cust_npc_scale_static.PR,
                cust_npc_scale_static.hp_regen,
                cust_npc_scale_static.spellscale,
                cust_npc_scale_static.healscale,
                cust_npc_scale_static.npcspecialattks,
                cust_npc_scale_static.usediabloloot,
                cust_npc_scale_static.localloottable
                FROM
                cust_npc_scale_static
                WHERE cust_npc_scale_static.zonesn = '" . $_[0] . "' OR cust_npc_scale_static.zonesn = 'global'";
        $query_handle = $connect->prepare($query);
        $query_handle->execute();
        $NTYPE = 0;
        while (@row = $query_handle->fetchrow_array()){ $N_SD{$row[0]} = [@row]; quest::gmsay("[Zonecontroller]: Loading static NPC data for NPC: '" . $row[0] ."'", 15); }
}

sub LoadPetTable{
        $connect = plugin::LoadMysql();
        $query = "SELECT
                pets.npcID,
                pets.type,
                pets.petpower,
                pets.temp,
                pets.petcontrol,
                pets.petnaming,
                pets.monsterflag,
                pets.equipmentset
                FROM
                pets
                ";
        $query_handle = $connect->prepare($query);
        $query_handle->execute();
        if($Debug){ quest::gmsay("[Zonecontroller]: Loading pet table...", 15); }
        while (@row = $query_handle->fetchrow_array()){ $PetD[$row[0]] = [@row]; if($Debug){ quest::gmsay("[Zonecontroller]: Loading Pet: '" . $row[0] . " - " . $row[1] . "'", 15); } }
        if($Debug){ quest::gmsay("[Zonecontroller]: Loading pet table complete", 15); }
}

sub LoadPetScaleData{
        $connect = plugin::LoadMysql();
        $query = "SELECT
                cust_pet_scaling_entries.level,
                cust_pet_scaling_entries.hp,
                cust_pet_scaling_entries.mana,
                cust_pet_scaling_entries.mindmg,
                cust_pet_scaling_entries.maxdmg,
                cust_pet_scaling_entries.attack_speed,
                cust_pet_scaling_entries.AC,
                cust_pet_scaling_entries.hp_regen,
                cust_pet_scaling_entries.spellscale,
                cust_pet_scaling_entries.healscale,
                cust_pet_scaling_entries.npcspecialattks
                FROM
                cust_pet_scaling_entries
                order by level
                ";
        $query_handle = $connect->prepare($query);
        $query_handle->execute();
        while (@row = $query_handle->fetchrow_array()){ $PetSD[$row[0]] = [@row]; }
        if($Debug){ quest::gmsay("[Zonecontroller]: Loading pet scaling complete", 15); }
}

sub ScaleNPCStatic{
        my $SS = $_[0];
        quest::shout("Found data for NPC " . $SS->GetCleanName() . ' - ' . $_[1]);
        $NTYPE = 0; $NN = "NPC";
        if(substr($SS->GetName(), 0, 1) eq "#" && substr($SS->GetName(), 1, 2) ne "#"){ $NTYPE = 1; $NN = "Named"; }
        if(substr($SS->GetName(), 0, 2) eq "##" && substr($SS->GetName(), 2, 3) ne "#"){ $NTYPE = 2; $NN = "Raid"; }

        $SS->ModifyNPCStat("max_hp", $N_SD{$_[1]}[2]);
        $SS->ModifyNPCStat("max_mana", $N_SD{$_[1]}[3]);
        $SS->ModifyNPCStat("min_hit", $N_SD{$_[1]}[4]);
        $SS->ModifyNPCStat("max_hit", $N_SD{$_[1]}[5]);
        $SS->ModifyNPCStat("attack_speed", $N_SD{$_[1]}[6]);
        $SS->ModifyNPCStat("ac", $N_SD{$_[1]}[7]);
        $SS->ModifyNPCStat("str", $N_SD{$_[1]}[8]);
        $SS->ModifyNPCStat("sta", $N_SD{$_[1]}[9]);
        $SS->ModifyNPCStat("dex", $N_SD{$_[1]}[10]);
        $SS->ModifyNPCStat("agi", $N_SD{$_[1]}[11]);
        $SS->ModifyNPCStat("int", $N_SD{$_[1]}[12]);
        $SS->ModifyNPCStat("wis", $N_SD{$_[1]}[13]);
        $SS->ModifyNPCStat("cha", $N_SD{$_[1]}[14]);
        $SS->ModifyNPCStat("mr", $N_SD{$_[1]}[15]);
        $SS->ModifyNPCStat("cr", $N_SD{$_[1]}[16]);
        $SS->ModifyNPCStat("dr", $N_SD{$_[1]}[17]);
        $SS->ModifyNPCStat("fr", $N_SD{$_[1]}[18]);
        $SS->ModifyNPCStat("pr", $N_SD{$_[1]}[19]);
        $SS->ModifyNPCStat("hp_regen", $N_SD{$_[1]}[20]);
        $SS->ModifyNPCStat("spellscale", $N_SD{$_[1]}[21]);
        $SS->ModifyNPCStat("healscale", $N_SD{$_[1]}[22]);
        $SS->ModifyNPCStat("special_attacks", $N_SD{$_[1]}[23]);
       
        if($N_SD{$_[1]}[24] == 1){ ### Tells Static Entry to use Diablo Loot
                $LID = (200000 + ($NTYPE * 1000) + $SS->GetLevel());
                if($SS->GetLoottableID() != $LID){
                        $SS->ModifyNPCStat("loottable_id", (210000 + ($NTYPE * 1000) + $SS->GetLevel())); $SS->AddLootTable();
                        $SS->ModifyNPCStat("loottable_id", (200000 + ($NTYPE * 1000) + $SS->GetLevel())); $SS->AddLootTable(); 
                }
        }
       
        if($N_SD{$_[1]}[25] > 0){ ### Add an additional Loot table
                $SS->ModifyNPCStat("loottable_id", $N_SD{$_[1]}[25]); $SS->AddLootTable(); 
        }
       
        $SS->SetEntityVariable("Scaled", 1); if($Debug){ $SS->SetEntityVariable("ScaledType", 2); }
}

sub RandomRange {

        my $MinRandom = $_[0];
        my $MaxRandom = $_[1];

        my $RandomResult = int(rand(($MaxRandom + 1) - $MinRandom)) + $MinRandom;
        if ($RandomResult > $MaxRandom)
        {
                return $MaxRandom;
        }
        return $RandomResult;

}


jshows1 07-27-2013 11:14 AM

Wow Thanks Akkadius. This sounds like a powerful tool. Real quick, how do you actually implement this? Do I simply create a mob in the zone I want and then put his pl file in that zone folder to get it to work?

Akkadius 07-27-2013 01:39 PM

Quote:

Originally Posted by jshows1 (Post 222772)
Wow Thanks Akkadius. This sounds like a powerful tool. Real quick, how do you actually implement this? Do I simply create a mob in the zone I want and then put his pl file in that zone folder to get it to work?

Well it's not a simple implementation, but as you can see in my notes, it is a NPC id of 50, it can be whatever you want it to be.

So how I achieve this automagically, is I insert it into the enterzone of the global_player.pl, if the NPC isn't in the zone when a player zones in, pop him. When he pops, he will go through the scaling routine.

If he isn't already a 'spawn' of the zone, I add him to a spawn regardless of instance version.

Code:

sub EVENT_ENTERZONE {
        if(!$entity_list->GetNPCByNPCTypeID(50)){
                quest::spawn2(50, 0, 0, 0, 0, 0, 0);
                $client->NPCSpawn($entity_list->GetNPCByNPCTypeID(50), "add", 1);
        } ### Automatic Scaling
}

Another question you might have. Yes, that's fine and dandy, but what about when a NPC spawns and needs to be scaled again?

Well, that's the main reason for me implementing global_npc.pl amongst other reasons.

Here is what I do, I send a signal to the zone controller, first with a unique signal telling the controller that 'HEY! I need to be scaled BEOTCH!' so the zone controller goes, ok yes I got it.

Once again, first he sends signal 21, then he sends the NPC Type ID. So the controller performs an iteration and only scales NPC's with that type ID.

Code:

sub EVENT_SPAWN {
        if($npc->GetEntityVariable("Scaled") != 1){ ### If not flagged as scaled, then scale the NPC
                quest::signalwith(50, 21, 0);
                quest::signalwith(50, $npc->GetNPCTypeID(), 0);
        }
}

The NPC recieves signal 21, sets a entity variable essentially 'flagging' itself for a split second like a catcher in a baseball game that he knows he is about to catch a NPCTypeID as a signal. As soon as he catches the NPCTypeID he releases the entity variable and gets ready for any other scaling requests.

So what did I achieve in the end with all of this?

TONS and TONS of time SAVED by having to scale my zones, I create generic 'ranges' that NPC's SHOULD be at based on their level AND type (Trash, named, raid). If I wanted to up the difficulty I had it set per zone with multipliers as you might be able to rip apart in my code.

All of the table pulls are custom, all of the framework needed to build a system like this is already there after implementing all of the source backend things. Through this thread I am revealing this for the first time.

Once I get my content release done, I will probably end up releasing a good chunk of my work including this one, but I've been so busy in the past years that is all variable.

If you have any questions regarding this type of implementation let me know.

jshows1 07-27-2013 02:13 PM

Akkadius, I for one am very grateful for you sharing this information. I will certainly be looking to use this. I'm sure I speak for the rest of the community when I offer my thanks. I'm sure this will make a lot of people happy to use this code and am looking forward to seeing what you end up implementing in the future.

Fridgecritter 03-06-2023 11:31 AM

Just a quick note that dimension is spelled correctly the first time, but the two times after that, it is spelled incorrectly.

##EDIT -- I just noticed the date on this post. Sorry for the necro.


All times are GMT -4. The time now is 10:07 PM.

Powered by vBulletin®, Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.