EQEmulator Forums

EQEmulator Forums (https://www.eqemulator.org/forums/index.php)
-   Support::Windows Servers (https://www.eqemulator.org/forums/forumdisplay.php?f=587)
-   -   Acceptable/efficient syntax (https://www.eqemulator.org/forums/showthread.php?t=40039)

rencro 09-29-2015 07:01 PM

Epic EMU did some work on pets zoning, can look what they did here:

https://github.com/epicemu/Server/bl...oning.cpp#L329

AdrianD 09-29-2015 07:33 PM

Thank you!

I've used <SetPet(0);> as kinda my mulligan. (I've had a few)

Trying to learn some other ways but...

AdrianD 09-30-2015 03:27 PM

I think I figured the pet rules to work as they should. It took way too long to figure out but, I learned a bit more about the system.

Everything appears to work although client crashed twice during testing. I logged into the emu well over a dozen times.

I am unsure if these rules will trump suspended minion AA. I will need to test that another time.

I put a couple very simple lines into two places and reverted everything I did previous.

Code:

near <void Client::BulkSendInventoryItems()> \zone\client_process.cpp(848)

// added 9/30/15 no rent check (this may need fixing when introducing suspended minion)
                if (!RuleB(Pets, PetLogPersistence))

                        SetPet(0);

near <void Client::Handle_OP_ZoneChange(const EQApplicationPacket *app) {> \zone\zoning.cpp(192)

// added 9/30/15 - removes pet on zoning (not on logging)
                if (!RuleB(Pets, PetZonePersistence))

                        SetPet(0);

The first occurs within the <deletenorent> code. I searched this area when I originally tried to do this a day or two ago but, was unsure of code to put in. It was also suggested by Uleat.

The following was put in just before what appears to send the client to the new coords, when zoning. This was a little more tricky because I didn't know how the zoning process worked. I ended up turning on all the logsys features to sleuth it out. Rencro made mention of this although at the time, I couldn't make sense of it.

If this could be improved, please let me know.

Thanks for your help.

Uleat 09-30-2015 03:59 PM

I would probably move your 'log persistence' check to here: https://github.com/EQEmu/Server/blob...cket.cpp#L1628

.. you could even make the database.LoadPetsInfo(this) call an else clause of the con check (PetLogPersistence == true)

I'm not 100% sure..but, that may clear up your client crashes too. (Looks like the profile packet was already sent to the client - with pet info - and you
were deleting the pet on the server afterwards.)

Mixing system methodologies can lead to issues down the road if you need to make changes, or if changes are made, to a code section.


The 'zone persistence' is probably ok (didn't look at the actual location) .. but, consider what you want to default behavior to be in case of a server crash, client ld, etc...

(Some) players will find a way to manipulate your best thought plans..even accidental actions can lead to exploitable discoveries.

AdrianD 09-30-2015 04:09 PM

Yes, the pets actually show up on the UI for a fraction of a second after logging in. I have been watching what happens in the DB the entire time and...

Quote:

(Looks like the profile packet was already sent to the client - with pet info - and you
were deleting the pet on the server afterwards.)
...seems to be true, for the log part.

The zone part deletes the entry immediately.

I will make it better. It's not exactly how I want even though it functions.

I'll check this out a little later, let it sit for a bit.

Thanks for your help.

EDIT: I tried as many variations as there are in that exact location you recommended Uleat. Although, I did not try anything after moving the bit to \zoning.cpp. I think I've let it sit long enough.

AdrianD 09-30-2015 07:51 PM

I reverted the bit in \client_process.cpp.

I could use a bit of assistance with something most would consider basic.

I need to insert something that checks the no rent code.

This is as far as I got:
Code:

        if (RuleB(Pets, PetLogPersistence) == false)
        {
                SetPet(0);
        }
               
        else (RuleB(Pets, PetLogPersistence) == true);
        {

                database.LoadPetInfo(this);
                /*
                This was moved before the spawn packets are sent
                in hopes that it adds more consistency...
                Remake pet
                */
                if (m_petinfo.SpellID > 1 && !GetPet() && m_petinfo.SpellID <= SPDAT_RECORDS) {
                        MakePoweredPet(m_petinfo.SpellID, spells[m_petinfo.SpellID].teleport_zone, m_petinfo.petpower, m_petinfo.Name, m_petinfo.size);
                if (GetPet() && GetPet()->IsNPC()) {
                        NPC *pet = GetPet()->CastToNPC();
                        pet->SetPetState(m_petinfo.Buffs, m_petinfo.Items);
                        pet->CalcBonuses();
                        pet->SetHP(m_petinfo.HP);
                        pet->SetMana(m_petinfo.Mana);
                        }
                m_petinfo.SpellID = 0;
                }
        }

This is some of the no rent code:
Code:

void Client::RemoveNoRent(bool client_update)


bool deletenorent = database.NoRentExpired(GetName());
        if (deletenorent) { //client was offline for more than 30 minutes, delete no rent items
                if (RuleB(Inventory, TransformSummonedBags))
                        DisenchantSummonedBags(false);
                RemoveNoRent(false);
        }

More than hints please.

Thanks

Uleat 09-30-2015 08:00 PM

I'm not seeing the end goal clearly defined...

You're wanting to check a client's norent items inside of a load pets function?

Shendare 09-30-2015 08:06 PM

Looks like he's wanting pets to poof like No Rent items. By the code snippet above, it looks like the best thing would be to move the "if PetLogPersistance==false { SetPet(0); }" bit to just after the "RemoveNoRent(false);" line within the "if (deletenorent) { }" block.

AdrianD 09-30-2015 08:26 PM

Thanks for the replies.

Quote:

it looks like the best thing would be to move the "if PetLogPersistance==false { SetPet(0); }" bit to just after the "RemoveNoRent(false);" line within the "if (deletenorent) { }" block.
Ah, I was thinking of going back to that. Thank you.

There are some basic understandings I am missing. Hopefully I'll get it soon and more will open up.

AdrianD 09-30-2015 08:55 PM

Ok.

I added a rule check <\zone\client_packet.cpp(1677)>
Code:

        if (RuleB(Pets, PetLogPersistence) == true)
        {
                database.LoadPetInfo(this);
                /*
                This was moved before the spawn packets are sent
                in hopes that it adds more consistency...
                Remake pet
                */
                if (m_petinfo.SpellID > 1 && !GetPet() && m_petinfo.SpellID <= SPDAT_RECORDS) {
                        MakePoweredPet(m_petinfo.SpellID, spells[m_petinfo.SpellID].teleport_zone, m_petinfo.petpower, m_petinfo.Name, m_petinfo.size);
                        if (GetPet() && GetPet()->IsNPC()) {
                                NPC *pet = GetPet()->CastToNPC();
                                pet->SetPetState(m_petinfo.Buffs, m_petinfo.Items);
                                pet->CalcBonuses();
                                pet->SetHP(m_petinfo.HP);
                                pet->SetMana(m_petinfo.Mana);
                        }
                        m_petinfo.SpellID = 0;
                }
        }

...and reverted this section <\zone\client_process.cpp(844)>
Code:

        bool deletenorent = database.NoRentExpired(GetName());
        if (deletenorent) { //client was offline for more than 30 minutes, delete no rent items
                if (RuleB(Inventory, TransformSummonedBags))
                        DisenchantSummonedBags(false);
                RemoveNoRent(false);
// added 9/30/15 no rent check (this may need fixing when introducing suspended minion)
        if (RuleB(Pets, PetLogPersistence) == false)
                {
                        SetPet(0);
                }
        }


As far as client crash from previous, I am uncertain what was causing it but, the wireless connection it had previous was not good. Getting to login server was difficult. I fixed that and logged a character with a pet (+30 minutes). I did not see the pet window but the pet was still an entry in the DB, like previous.

I will review what you said about this Uleat.

Thanks

EDIT: something isn't right with the norent check - I'll check back but, I've had enough for now

Uleat 09-30-2015 10:11 PM

SetPet() is an inherited function from the class Mob: https://github.com/EQEmu/Server/blob.../pets.cpp#L542

SetPet(0), when called from the Client class, is essentially doing this:
Code:

((Mob*)client_inst)->SetPet(nullptr);
There are no Client class-based methods performed at this level and all that is occurring is that you're setting the memory reference for the client's pet to nullptr.

This is ok for other mob-derived classes that don't save their pet buffs, inventories, etc... (they still clean-up as required, though)

But, for client, you should to handle it somewhere at the Client class level..meaning database clean-up, and deletion of any possible pet instances from memory..otherwise,
you will end up with a memory leak (allocated..but, no longer used memory that is not released back to the system - the losses add up)

(If you handle the action before creating an actual in-memory reference, then you won't need to worry about the clean-up.)

AdrianD 10-01-2015 02:27 AM

I appreciate the explanations. I wouldn't have known if you didn't say anything about it. I will digest this in the coming days and try to apply this if I can.

AdrianD 10-01-2015 05:25 PM

I reverted everything that was working with PetLogPersistence. The issue previous was simple and something I overlooked as I quickly wrote it in as I was out the door.

I went back to \zone\client_process.cpp and added queries to the code.

The results are a little better. The entries are deleted until zoning or casting a new pet when new ones are made. I attempted to add database.SavePetInfo(this); <void ZoneDatabase::SavePetInfo(Client *client)> but the result wasn't desireable. (because of <PetInfo *petinfo = nullptr;> happening first maybe?)

The pet window still appears for a short time and the pet=0 entries are non-existent for the char logged 30+ minutes after initial login. (/q resets the entries - unsure if this = LD)

Code:

        bool deletenorent = database.NoRentExpired(GetName());
        if (deletenorent) { //client was offline for more than 30 minutes, delete no rent items
                if (RuleB(Inventory, TransformSummonedBags))
                        DisenchantSummonedBags(false);
                RemoveNoRent(false);

// added 9/30/15 no rent check (this may need fixing when introducing suspended minion - if this was an issue the queries below should fix it)
                if (!RuleB(Pets, PetLogPersistence))
                {
                        SetPet(0);
                       
                        std::string query = StringFormat("DELETE FROM `character_pet_info` WHERE `char_id` = %u AND character_pet_info.pet = 0", CharacterID());
                        auto results = database.QueryDatabase(query);
                       
                        query = StringFormat("DELETE FROM `character_pet_buffs` WHERE `char_id` = %u AND character_pet_info.pet = 0", CharacterID());
                        results = database.QueryDatabase(query);
                       
                        query = StringFormat("DELETE FROM `character_pet_inventory` WHERE `char_id` = %u AND character_pet_info.pet = 0", CharacterID());
                        results = database.QueryDatabase(query);                                               
                }
        }


If I knew how to set these queries up in zonedb.cpp and make the connections to it from client_process, I would have. This really isn't that difficult, the concept, the web of connections. It's the syntax or C++ grammar that is the pain in the ass, the huge hindrance for me. I can figure out how the EQEmu system works just by exposure and asking questions where the answer is fairly simple, as has been mentioned.

Thanks

Uleat 10-01-2015 06:49 PM

I wanted to see if there was an existing method to 'delete' pets from clients..but, the only one I found only deleted the inventories and buffs..so, I'm not sure where that
is handled...

(I mean, when a pet dies, it is deleted, right? Otherwise, it would respawn when a client enters the world.)

Shendare 10-01-2015 07:11 PM

Is there just a Pet->Death()? lol

Uleat 10-01-2015 07:37 PM

Well, I followed case GETLOST: back, and SavePetInfo() .. nothing I saw removed the actual pet instance.

I dunno? :P


I know I'm missing something...

AdrianD 10-01-2015 07:43 PM

There is a depop in \spawn2.h and \spawn2.cpp:

Code:

void        Depop();

void Spawn2::Depop() {
        timer.Disable();
        Log.Out(Logs::Detail, Logs::Spawns, "Spawn2 %d: Spawn reset, repop disabled", spawn2_id);
        npcthis = nullptr;
}

I saw this while sifting through potential options.

There is different depop for zone.

AdrianD 10-01-2015 08:55 PM

A bit of progress, not much though.

No entry being created like it does when zoning and using <SetPet(0);>

\zonedb.cpp - last 1/2 commented out, unsure what to do to make it create a new blank entry

When the part /* */ is uncommented it only removes `spell_id` in the DB - pet_struct maybe?
Code:

void ZoneDatabase::DeletePetInfo(Client *client)
{
        std::string query = StringFormat("DELETE FROM `character_pet_info` WHERE `char_id` = %u AND `pet` = 0", client->CharacterID());
        auto results = database.QueryDatabase(query);
        if (!results.Success())
                return;

        query = StringFormat("DELETE FROM `character_pet_buffs` WHERE `char_id` = %u AND `pet` = 0", client->CharacterID());
        results = database.QueryDatabase(query);
        if (!results.Success())
                return;

        query = StringFormat("DELETE FROM `character_pet_inventory` WHERE `char_id` = %u AND `pet` = 0", client->CharacterID());
        results = database.QueryDatabase(query);
        if (!results.Success())
                return;

//        PetInfo *petinfo = nullptr;
        /*
        for (int pet = 0; pet < 2; pet++) {
        PetInfo        *petinfo = client->GetPetInfo(pet);
                if (!petinfo)
                        continue;

                query = StringFormat("INSERT INTO `character_pet_info` "
                        "(`char_id`, `pet`, `petname`, `petpower`, `spell_id`, `hp`, `mana`, `size`) "
                        "VALUES (%u, %u, '%s', %i, %u, %u, %u, %f) "
                        "ON DUPLICATE KEY UPDATE `petname` = '%s', `petpower` = %i, `spell_id` = %u, "
                        "`hp` = %u, `mana` = %u, `size` = %f",
                        client->CharacterID(), pet, petinfo->Name, petinfo->petpower, petinfo->SpellID,
                        petinfo->HP, petinfo->Mana, petinfo->size, // and now the ON DUPLICATE ENTRIES
                        petinfo->Name, petinfo->petpower, petinfo->SpellID, petinfo->HP, petinfo->Mana, petinfo->size);
                results = database.QueryDatabase(query);
                if (!results.Success())
                        return;
                query.clear();
                }        */
}

\client_process.cpp
Code:

bool deletenorent = database.NoRentExpired(GetName());
        if (deletenorent) { //client was offline for more than 30 minutes, delete no rent items
                if (RuleB(Inventory, TransformSummonedBags))
                        DisenchantSummonedBags(false);
                RemoveNoRent(false);

// added 9/30/15 no rent check (this may need fixing when introducing suspended minion - if this was an issue the queries below should fix it)
                if (!RuleB(Pets, PetLogPersistence))
                {                               
                        SetPet(0);
                       
                        database.DeletePetInfo(this);                       
                }
        }

Thanks

AdrianD 10-02-2015 12:42 AM

Added to \client_process.cpp - it's a little smoother, adds the blank row and saves it. The client still shows the pet window momentarily. I would prefer to put <memset(&m_petinfo, 0, sizeof(struct PetInfo));> in the DeletePetInfo part and then run w/e queries are needed to fill in the blanks allowing the removal of <database.SavePetInfo(this);> below.

I would prefer this whole process done a little earlier. Just a guess but, somewhere near <case ServerOP_SyncWorldTime:> (\zone\worldserver.cpp(744).

This also leaves out what Uleat said before about memory loss? which I have no clue about.

\client_process.cpp(841)
Code:

// added 9/30/15 no rent check (this may need fixing when introducing suspended minion - if this was an issue the queries below should fix it)
                if (!RuleB(Pets, PetLogPersistence))
                {
                        SetPet(0);

                        database.DeletePetInfo(this);

                        memset(&m_petinfo, 0, sizeof(struct PetInfo));
                       
                        database.SavePetInfo(this);
                }
        }


AdrianD 10-02-2015 03:06 AM

Last one for a bit:

The two lines below SetPet, <Mob* mypet =......etc.>, gives the same resulting appearance as <SetPet(0);>. (takes a moment to depop) I am unsure if one is better than the other.

\client_process.cpp(841)
Code:

                if (!RuleB(Pets, PetLogPersistence))
                {
                        SetPet(0);

//                    or

                        Mob* mypet = this->GetPet();
                        mypet->CastToNPC()->Depop();

                        database.DeletePetInfo(this);

                        memset(&m_petinfo, 0, sizeof(struct PetInfo));
                       
                        database.SavePetInfo(this);
                }
        }


AdrianD 10-03-2015 10:26 AM

A little more progress on the pet thing.

The entire delete/save process (creating a new blank entry in table `character_pet_info`) for the DB in one spot. This eliminates running a couple unneeded queries from using the <SavePetInfo> function.

The pet window still appears on client. I don't know if this is avoidable by doing the norent check somewhere earlier in the process. It's not a big deal unless there is a cleaner/more efficient way to do this.

\client_process.cpp
Code:

        bool deletenorent = database.NoRentExpired(GetName());
        if (deletenorent) { //client was offline for more than 30 minutes, delete no rent items
                if (RuleB(Inventory, TransformSummonedBags))
                        DisenchantSummonedBags(false);
                RemoveNoRent(false);

// added 9/30/15 no rent check (updated 10/3/15)
                if (!RuleB(Pets, PetLogPersistence))
                {
                        SetPet(0);

                        database.DeletePetInfo(this);
                }
        }

\zonedb.cpp
Code:

void ZoneDatabase::DeletePetInfo(Client *client)
{
        std::string query = StringFormat("DELETE FROM `character_pet_info` WHERE `char_id` = %u AND `pet` = 0", client->CharacterID());
        auto results = database.QueryDatabase(query);
        if (!results.Success())
                return;

        query = StringFormat("DELETE FROM `character_pet_buffs` WHERE `char_id` = %u AND `pet` = 0", client->CharacterID());
        results = database.QueryDatabase(query);
        if (!results.Success())
                return;

        query = StringFormat("DELETE FROM `character_pet_inventory` WHERE `char_id` = %u AND `pet` = 0", client->CharacterID());
        results = database.QueryDatabase(query);
        if (!results.Success())
                return;

        PetInfo *petinfo = client->GetPetInfo(0);

        memset(petinfo, 0, sizeof(struct PetInfo));

        int pet = 0;

        query = StringFormat("INSERT INTO `character_pet_info` "
                "(`char_id`, `pet`, `petname`, `petpower`, `spell_id`, `hp`, `mana`, `size`) "
                "VALUES (%u, %u, '%s', %i, %u, %u, %u, %f) "
                "ON DUPLICATE KEY UPDATE `petname` = '%s', `petpower` = %i, `spell_id` = %u, "
                "`hp` = %u, `mana` = %u, `size` = %f",
                client->CharacterID(), pet, petinfo->Name, petinfo->petpower, petinfo->SpellID,
                petinfo->HP, petinfo->Mana, petinfo->size, // and now the ON DUPLICATE ENTRIES
                petinfo->Name, petinfo->petpower, petinfo->SpellID, petinfo->HP, petinfo->Mana, petinfo->size);
        results = database.QueryDatabase(query);
        if (!results.Success())
                return;
        query.clear();
}

As an aside: While going through this process I noticed some queries are ran anywhere between 2-6 times from this maybe? <database.QueryDatabase(query);>

I understand my tinkering may have caused some of this but, I'm curious to know the purpose of this.

Thanks

EDIT:

Not sure if I mentioned this before. I had to add the line below when I created the item in \zonedb.cpp. Can't take it for granted.

\zonedb.h
Code:

        void DeletePetInfo(Client *c);

AdrianD 10-09-2015 03:30 PM

Good day.

I created a rule to force training of certain melee skills. The intention is to only force training if the skill is acquired after level 1.

For Riposte below,

\zone\attack.cpp(412)
Code:

                if (IsClient()) {
                        if (!RuleB(Skills, TrainMeleeSkills)) {
                                CastToClient()->CheckIncreaseSkill(SkillRiposte, other, -10);
                        }
                        else if (RuleB(Skills, TrainMeleeSkills) && CastToClient()->HasSkill(SkillRiposte)) {
                                (CastToClient()->CheckIncreaseSkill(SkillRiposte, other, -10));
                        }
                }

I'm concerned about what will happen when the rule is true and the character does not have <SkillRiposte>. Do I need some kind of escape from this if it goes beyond <else if>? There is quite a bit more code after what is quoted above.

This question applies to several similar issues I had concern about.

Thanks

Uleat 10-09-2015 04:48 PM

If there's nothing left to process in the function after your 'else if' and the exclusion can be caught with an 'else' or another 'else if' statement, you can simply handle it with a 'return' statement.

Otherwise, you might consider putting your post-con check code inside of a single con check that meets all of the requirements for that code to be processed.

AdrianD 10-09-2015 08:36 PM

Thank you sir.

Quote:

If there's nothing left to process in the function after your 'else if' and the exclusion can be caught with an 'else' or another 'else if' statement, you can simply handle it with a 'return' statement.
I understand this.

Quote:

Otherwise, you might consider putting your post-con check code inside of a single con check that meets all of the requirements for that code to be processed.
I'm a little fuzzy on this.

I can't find it but I thought I saw an instance where <else> was used with nothing between { } except spaces.

EDIT: Here but this may not apply to my issue.
Code:

else {
                                if (!GetFeigned() && (DistanceSquared(bindmob->GetPosition(), m_Position)  <= 400)) {
                                        // send bindmob bind done
                                        if(!bindmob->IsAIControlled() && bindmob != this ) {

                                        }
                                        else if(bindmob->IsAIControlled() && bindmob != this ) {
                                        // Tell IPC to resume??
                                        }
                                        else {
                                        // Binding self
                                        }
                                        // Send client bind done

                                        bind_out->type = 1; // Done
                                        QueuePacket(outapp);
                                        bind_out->type = 0;
                                        CheckIncreaseSkill(SkillBindWound, nullptr, 5);

                                        int maxHPBonus = spellbonuses.MaxBindWound + itembonuses.MaxBindWound + aabonuses.MaxBindWound;


Uleat 10-09-2015 09:45 PM

Just wasted code space..though it may be there, with a remark, to indicate what is happening to arrive there or an area for future implementations.

The compiler will probably optimize out that particular else clause since there is nothing being processed.

AdrianD 10-09-2015 09:48 PM

Gotcha, thanks.

AdrianD 10-13-2015 02:27 PM

I've tried a different approach with no success, so I try here:

I'm trying to come up with a script which, I hope, will summon a corpse using the necro spell, id = 3 (summon corpse).

I've looked at the Dragons of Norrath and the guild lobby quests and they will no do what I want them to do. I want to summon a corpse with the same behavior as the NEC spell "summon corpse".

Everything appears to work except self targeting. Since the npc is targeted at the end of the script and the spell is self (player) cast, the text stating "Your target must be a group member for this spell." appears. I've also tried to change the actual spell effect code.

Below is what I have so far with <location of target code> to indicate where I think it should belong.

Code:

-- global\a_dungeon_necromancer.lua NPCID

function event_say(e)
        if(e.message:findi("hail")) then
                e.self:Say("I know the reason you come to see me, " .. e.other:GetName() .. ". Don't be afraid, the living do not concern me... You require my [services], you must ask for them. Only the willing are allowed when living.");
        elseif(e.message:findi("services")) then
                e.self:Say("" .. e.other:GetName() .. ", to perform the necessary incantation, you must hand me one platinum, three gold, three silver and seven copper pieces.");
        end
end

function event_trade(e)
        local item_lib = require("items");
        local leet_cash = 0;
        if(item_lib.check_turn_in(e.trade, {platinum = 1, gold = 3, silver = 3, copper = 7})) then

                leet_cash = 1;
        end

        if(leet_cash >= 1) then

--                        <location of target code>
                       
                        eq.SelfCast(3);
                        if(leet_cash == 1) then
                                e.self:Say("Believe me when I say this exact amount represents the services offered.");
                                leet_cash = 0;
                end

        end
        item_lib.return_items(e.self, e.other, e.trade)
end


If anyone knows how to accomplish what I am trying to do, please share.

Thanks

provocating 10-15-2015 12:32 AM

I do not even know why I am trying to help you at this point, but whatever.

You never explained WHY the guild summoner scripts could not be modified to do what you want. I mean they summon all of the players corpses. How does that not work?

AdrianD 10-15-2015 01:02 AM

Quote:

I do not even know why I am trying to help you at this point, but whatever.

You never explained WHY the guild summoner scripts could not be modified to do what you want. I mean they summon all of the players corpses. How does that not work?
I'm sorry if I hurt you, provocating. I can tell you for certain, I do not feel the same way. Grudges aren't healthy.

If I knew how to alter <EVENT_SUMMON();> to only work in specific zones or with specific corpses, I would not be asking the question.

Maybe it's clarification I need. I don't have much knowledge of scripts and this is why I asked the question to begin with, hoping, someone could clarify this.

AdrianD 10-15-2015 01:32 AM

$client->SetTarget($client);

That's all that was needed in the .pl file.

Code:

# global\a_dungeon_necromancer.pl NPCID


sub EVENT_SAY  {
        if ($text=~/hail/i) {
                quest::say("I know the reason you come to see me, $name. Don't be afraid, the living do not concern me... You require my [services], you must ask for them. Only the willing are allowed when living.");
        }
        if ($text=~/services/i) {
            quest::say("$name, to perform the necessary incantation, you must hand me one platinum, three gold, three silver and seven copper pieces.");

        }
}

sub EVENT_ITEM {
        if (plugin::takeCoin(1000)) {
                quest::say("Believe me when I say this exact amount represents the services offered and becomes transmuted into the much larger pile of where, I only know. HAHA!");
                $client->SetTarget($client);
                quest::selfcast(3);
        }
        plugin::returnUnusedItems();
}

Thanks

PS. Could use a writer.

Noport 10-15-2015 04:51 AM

Feel free to try this have no idea where npc location or for what zone i had no way to test this perl script.

a_dungeon_necromancer.pl
Code:

EVENT_SAY  {
if ($text=~/hail/i) {
quest::say("I know the reason you come to see me, $name. Don't be afraid, the living do not concern me... You require my [services], you must ask for them. Only the willing are allowed when living.");
}
if ($text=~/services/i) {
quest::say("$name, to perform the necessary incantation, you must hand me one platinum, three gold, three silver and seven copper pieces.");
}
}
sub EVENT_ITEM{
if (($platinum>=1) (($Gold>=3) ((Silver>=3) (($Copper>=7{)) then
$npc->SetAppearance(0);
$client->Message("Believe me when I say this exact amount represents the services offered.");
quest::summonburriedplayercorpse($charid, $x, $y, $z, 0);
$corpse = 0;
$charid = 0;
else{
quest::say("Thank you for your donation $name, it wasn't enough though ...");
 }
}
sub EVENT_ITEM {
quest::say("I have no use for this, $name.");
plugin::return_items(\%itemcount);
}


AdrianD 10-15-2015 02:49 PM

Thank you Noport.

AdrianD 10-16-2015 04:15 PM

I added a mechanic to <channelchance> and am wondering if it looks right or if it could be done better. It may be a little confusing but the purpose was learning as much as adding this code. It's an exercise in futility trying to channel at lower levels and the added mechanic mitigates this a bit. It also reinforces those who practice skills when they level, like the old days.

Also, the <distance_moved> and <distancemod> formulas do not do as advertised. The idea seems proper but the implementation is off. I will add to this my results when I get to it.

The big thing I am wondering is if the code in the header is returning the value for the previous level max skill. This mechanic seems to work upon initial testing.

\zone\client.h(695)
Code:

        inline uint16 PrevMaxSkill(SkillUseTypes skillid) const { return MaxSkill(skillid, GetClass(), GetLevel()-1); }
\zone\spells.cpp(1105) - <void Mob::CastedSpellFinished>
Code:

                                // max 93% chance at 252 skill
                                channelchance = 30 + GetSkill(SkillChanneling) / 400.0f * 100;
                               
                                uint16 cS = CastToClient()->GetRawSkill(SkillChanneling); // current skill
                                uint16 mS = CastToClient()->MaxSkill(SkillChanneling); // current level max skill
                                uint16 pS = CastToClient()->PrevMaxSkill(SkillChanneling); // current level - 1 max skill
                                double iA = 20 / (mS - pS); // adjustment interval
                                double cA = 0; // cA = adjustement coefficient
                                double param, result;
                                param = (pow((.0005*cS), 2));

                                while (true)
                                {
                                        if (cS >= 200) {
                                                break;
                                        }                                       
                                        else if (cS <= pS) {
                                                break;
                                        }
                                        else if (GetLevel () <= 1) {
                                                cA = (cS * 2);
                                                result = cA - param;
                                                channelchance += result;
                                                break;
                                        }
                                        else {
                                                cA = ((cS - pS)*iA);
                                                result = cA - param;
                                                channelchance += result;                                               
                                        }
                                        break;
                                }
                                channelchance -= attacked_count * 2;
                                channelchance += channelchance * channelbonuses / 100.0f;
                        }

<snip code>
                                        distance_moved = d_x * d_x + d_y * d_y;
                                        // if you moved 1 unit, that's 25% off your chance to regain.
                                        // if you moved 2, you lose 100% off your chance
                                        distancemod = distance_moved * 25;
                                        channelchance -= distancemod;
                                }

Thanks

EDIT: here is a visualization of the behavior http://prnt.sc/8s311y

The blue line is the current, unadjusted channelchance. The lower red function is the formula I added. The upper red function is max skill channelchance after adding the the formula.

AdrianD 10-16-2015 09:01 PM

Quote:

Also, the <distance_moved> and <distancemod> formulas do not do as advertised. The idea seems proper but the implementation is off. I will add to this my results when I get to it.
I think I misinterpreted this. Disregard.


All times are GMT -4. The time now is 12:11 AM.

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