Giter Site home page Giter Site logo

Comments (8)

namreeb avatar namreeb commented on May 18, 2024

Chronological list of control flow divergence

0x6E4D43: Check to prevent error spam when the player is casting the currently-casting spell repeatedly.

  if ( spellId == s_modalSpellID )
  {
    SndInterfacePlayInterfaceSound("igPlayerInviteDecline");
    return false;
  }

Solution: Replace JNZ 0x6E4D62 with JMP 0x6E4D62 at 0x6E4D49.

0x6E4DE3: If a modal spell (one which created a cast bar) is underway, report that another spell is in progress.

  if ( Spell_C_IsModal() )
  {
    modalSpell = s_modalSpellID < 0 || s_modalSpellID > g_spellDB.m_maxID ? NULL : g_spellDB.m_recordsById[s_modalSpellID];
    if ( !(modalSpell->Attributes & (SPELL_ATTR_ON_NEXT_SWING_2|SPELL_ATTR_ON_NEXT_SWING_1)) )
    {
      Spell_C_SpellFailed(v44->Id, SPELL_FAILED_SPELL_IN_PROGRESS, -1, -1);
      return false;
    }
  }

Solution: If we end up being successful at changing when the cast bar appears, this probably wont be necessary to bypass. For now, it can be bypassed by changing JZ 0x6E4DE6 with JMP 0x6E4DE6 at 0x6E4D9E.

Edit: Another benefit to not bypassing this check (if it's true that we don't need to) is that it will reduce spam to the server.

from nampower.

namreeb avatar namreeb commented on May 18, 2024

Error Handling

This post assumes the two above patches have been made.

When a spell is casting, and the same spell is cast again, two different errors are reported: spell not ready, and interrupted (and the original spell cast is interrupted). This interruption comes from CGUnit_C::CheckAndReportSpellInhibitFlags(), cast by Spell_C_CastSpell() at 0x6E4F3B:

  if ( !RangeCheckSelected(this, spell, guid, 1) || !CGUnit_C::CheckAndReportSpellInhibitFlags(this, spell, a2) )
  {
    return false;
  }

The logic in CGUnit_C::CheckAndReportSpellInhibitFlags() is as follows:

    // is this spell on cooldown?
    if ( Spell_C_GetSpellCooldown(spellRec->Id, 0, 0, 0, 0) )
    {
      repeatId = GetAutoRepeatSpellId();
      if ( repeatId >= 0 && repeatId <= g_spellDB.m_maxID )
      {
        repeatSpell = g_spellDB.m_recordsById[repeatId];
        if ( repeatSpell )
        {
          // should the current cast be cancelled?
          if ( repeatSpell->AttributesEx3 & SPELL_ATTR_EX3_REQ_WAND )// <-- bad name
          {
            pkt.m_buffer = 0;
            pkt.m_base = 0;
            pkt.m_alloc = 0;
            pkt.m_size = 0;
            pkt.m_read = -1;
            pkt.VMT = (CDataStoreVMT *)CDataStore::`vftable';
            CDataStore::Put32(&pkt, 0x12F);     // CMSG_CANCEL_CAST
            repeatId = GetAutoRepeatSpellId();
            CDataStore::Put32(&pkt, repeatId);
            pkt.m_read = 0;
            ClientServices_Send(&pkt);
            Send_CMSG_CANCEL_AUTO_REPEAT_SPELL();
            pkt.VMT = (CDataStoreVMT *)CDataStore::`vftable';
            if ( pkt.m_alloc != -1 )
              CDataStoreVMT_InternalDestroy(&pkt, &pkt.m_buffer, &pkt.m_base, &pkt.m_alloc);
          }
        }
      }
      // fail: not ready yet
      Spell_C_SpellFailed(spellRec->Id, SPELL_FAILED_NOT_READY, -1, -1);
      return false;
    }

Note that the check for the auto repeating spell is not relevant to our task. I think there is no problem with CGUnit_C::CheckAndReportSpellInhibitFlags() calling Spell_C_SpellFailed() when the requested spell is on cooldown, but Spell_C_SpellFailed() will call Spell_C_CancelSpell() if the failed spell is the current modal.

Solution: Replace JNZ 0x6E2228 with JMP 0x6E2228 at 0x6E2207.

At this point, there is no unexpected behavior, but no new behavior either. The next step is to find why CMSG_CAST_SPELL is not sent. This should give some indication of what state needs to be updated when a spell cast is attempted, rather than when the server acknowledges it.

EDIT: There is a problem with allowing this message in that if CGUnit_C::CheckAndReportSpellInhibitFlags() returns false, the cast will not proceed. To bypass this check, replace JZ 0060962C with JMP 0060962C at 0x609570.

from nampower.

namreeb avatar namreeb commented on May 18, 2024

Normal Spell Cast

This usually comes from a sprite click (specifically, the action bar). Consequently, the spell cast is triggered by Spell_C_HandleSpriteClick(). The call stack on a normal call looks like this:

  1. Spell_C_CastSpell (0x6E4B60)
  2. Spell_C_TargetSpell (0x6E5250)
  3. Spell_C_HandleSpriteClick (0x6E5B40)
  4. SendCast (0x6E54F0)

Spell_C_CastSpell() is always called, even if a spell cast is already underway. Spell_C_TargetSpell(), however, is not. Therefore the control flow divergence is somewhere within Spell_C_CastSpell(). A few reasons why are mentioned in the first comment, however these are all cases that should be okay to keep. What we really want to find is what state is cleared when a response is received from the server.

CMSG_CAST_SPELL does not appear to be sent to the server until after SMSG_CAST_RESULT is received. The handler for this function is at 0x6E7A70.

from nampower.

namreeb avatar namreeb commented on May 18, 2024

Possible Solution 1

Hook Spell_C_CastSpell. If it returns true, a successful cast was sent. Begin a timer for the duration of the cast. Once the timer elapses, reset s_modalSpellID. How do we find the cast time? Remember to account for haste buffs (i.e. Mind Quickening Gem) and push-back from damage. The cast bar accounts for these things, so surely they are calculated somewhere?

Spell push-back is controlled by the server (since it most roll for pushback resist mechanics). The client is informed of delays via SMSG_SPELL_DELAYED. However this opcode is handled by the built-in lua interface code, and there is no convenient method of reading this. Therefore we probably will have to calculate the expected cast time, considering haste (de)buffs, and adjust based on intercepting SMSG_SPELL_DELAYED. This does not handle the case of casting a spell before the removal of a haste buff has arrived at the client, however.

CGPlayer_C::GetSpellCastingTime() is at 0x5EE150, and is cast by:

  1. Spell_C_CastSpell at 0x6E4FF5, which is just a check for non-zero cast time.
  2. CGUnit_C::PlayImpactKit at 0x60F374.

from nampower.

namreeb avatar namreeb commented on May 18, 2024

Possible Solution 2

Begin the cast bar when the cast is sent, rather than when it is acknowledged by the server. The cast bar is controlled by the interface files internal to the games MPQ files. Specifically: Interface\FrameXML\CastingBarFrame.lua. The cast bar is first created by the SPELLCAST_START event, which has a numeric id of 337.

The SPELLCAST_START event is triggered by the handler for SMSG_SPELL_START at 0x6E7A53:

FrameScript_SignalEvent(EVENT_SPELLCAST_START, "%s%d", spellName, msCastTime);

This is not a complete solution in and of itself. In very rare instances, we will have outdated information about our own spell haste. Our haste may be lower than we are aware, and in extremely rare conditions it may be higher than we are aware (although I think this is practically impossible except with extremely high latency, greater than the duration of the global cooldown). In the event that our haste is lower than we are aware, there shouldn't be any problem, as our cast will actually finish before the cast bar does. In the event that our haste is higher than we are aware, there will still be a slight unnecessary delay before the next cast may complete, but this will be left as a future enhancement to resolve (since in reality it will probably never happen).

To achieve this solution, the call to FrameScript_SignalEvent(337) at 0x6E7A53 must be removed, and an equivalent call to FrameScript_SignalEvent(337) must be added upon the successful completion of Spell_C_CastSpell.

from nampower.

tserafin avatar tserafin commented on May 18, 2024

Hi namreeb,

I am quite interested in your progress on this ticket and am wondering how you are tracking with this?

Regards

from nampower.

namreeb avatar namreeb commented on May 18, 2024

Hello, @tserafin. Thank you for your interest. I have started writing code for this and testing it in-game, but it is not quite working yet. I don't have an ETA. In fact, I'm still not even certain that this is a viable solution. Initial results are encouraging, though.

from nampower.

namreeb avatar namreeb commented on May 18, 2024

I have just pushed code for version 2.0, which solves this issue. I do not plan to upload binaries for it until I can test it a bit more.

from nampower.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.