internal static void WriteSpellData(BinaryWriter writer, D20SpellData spellData)
    {
        // The rest is mostly packed in 4-bit numbers
        Span <byte> packedSpellData = stackalloc byte[8];

        var metaMagicPacked = spellData.metaMagicData.Pack();

        BitConverter.TryWriteBytes(packedSpellData.Slice(1, 4), metaMagicPacked);
        // The upper 8-bit of the spell enum will bleed into the metamagic data,
        // but since the packed metamagic data only uses 3 byte, this doesnt matter
        BitConverter.TryWriteBytes(packedSpellData.Slice(0, 2), (ushort)spellData.spellEnumOrg);

        // TODO: Might need unchecked cast because of the 0x80 flag?
        packedSpellData[5] = (byte)spellData.spellClassCode;

        if (spellData.HasItem)
        {
            packedSpellData[6] = (byte)spellData.itemSpellData;
        }
        else
        {
            packedSpellData[6] = 0xFF;
        }

        packedSpellData[7] = (byte)(((byte)spellData.spontCastType & 0xF) << 4
                                    | (spellData.spellSlotLevel & 0xF));

        writer.Write(packedSpellData);
    }
Esempio n. 2
0
    public static void CastSpell(this GameObject caster, int spellEnum, GameObject targetObj = null)
    {
        var spellClasses = new List <int>();
        var spellLevels  = new List <int>();

        if (!GameSystems.Spell.TryGetSpellEntry(spellEnum, out var spellEntry))
        {
            Logger.Warn("Trying to cast unknown spell: {0}", spellEnum);
            return;
        }

        SpellPacketBody spellPktBody = new SpellPacketBody();

        spellPktBody.spellEnum         = spellEnum;
        spellPktBody.spellEnumOriginal = spellEnum;
        spellPktBody.caster            = caster;
        if (!GameSystems.Spell.SpellKnownQueryGetData(caster, spellEnum, spellClasses, spellLevels))
        {
            return;
        }

        for (var i = 0; i < spellClasses.Count; i = ++i)
        {
            if (!GameSystems.Spell.spellCanCast(caster, spellEnum, spellClasses[i], spellLevels[i]))
            {
                continue;
            }

            spellPktBody.spellKnownSlotLevel = spellLevels[i];
            spellPktBody.spellClass          = spellClasses[i];
            GameSystems.Spell.SpellPacketSetCasterLevel(spellPktBody);

            var pickArgs = new PickerArgs();
            GameSystems.Spell.PickerArgsFromSpellEntry(spellEntry, pickArgs, caster, spellPktBody.casterLevel);
            pickArgs.flagsTarget &= ~UiPickerFlagsTarget.Range;
            pickArgs.flagsTarget |= UiPickerFlagsTarget.LosNotRequired;

            LocAndOffsets?loc = null;
            if (pickArgs.modeTarget.IsBaseMode(UiPickerType.Single) ||
                pickArgs.modeTarget.IsBaseMode(UiPickerType.Multi))
            {
                loc = targetObj?.GetLocationFull();
                pickArgs.SetSingleTgt(targetObj);
            }
            else if (pickArgs.modeTarget.IsBaseMode(UiPickerType.Cone))
            {
                loc = targetObj?.GetLocationFull();
                if (loc.HasValue)
                {
                    pickArgs.SetConeTargets(loc.Value);
                }
            }
            else if (pickArgs.modeTarget.IsBaseMode(UiPickerType.Area))
            {
                if (spellEntry.spellRangeType == SpellRangeType.SRT_Personal)
                {
                    loc = caster.GetLocationFull();
                }
                else
                {
                    loc = targetObj?.GetLocationFull();
                }

                if (loc.HasValue)
                {
                    pickArgs.SetAreaTargets(loc.Value);
                }
            }
            else if (pickArgs.modeTarget.IsBaseMode(UiPickerType.Personal))
            {
                loc = caster.GetLocationFull();
                pickArgs.SetSingleTgt(caster);
            }

            GameSystems.Spell.ConfigSpellTargetting(pickArgs, spellPktBody);
            if (spellPktBody.Targets.Length > 0 && GameSystems.D20.Actions.TurnBasedStatusInit(caster))
            {
                GameSystems.D20.Actions.GlobD20ActnInit();
                GameSystems.D20.Actions.GlobD20ActnSetTypeAndData1(D20ActionType.CAST_SPELL, 0);
                GameSystems.D20.Actions.ActSeqCurSetSpellPacket(spellPktBody, true);
                var spellData = new D20SpellData(spellEnum, spellClasses[i], spellLevels[i]);
                GameSystems.D20.Actions.GlobD20ActnSetSpellData(spellData);
                GameSystems.D20.Actions.GlobD20ActnSetTarget(targetObj, loc);
                GameSystems.D20.Actions.ActionAddToSeq();
                GameSystems.D20.Actions.sequencePerform();
                break;
            }
        }
    }
Esempio n. 3
0
    private void SetSpontaneousCastingAltNode(GameObject obj, int nodeIdx, SpellStoreData spellData)
    {
        var spellClassCode = spellData.classCode;

        if (GameSystems.Spell.IsDomainSpell(spellClassCode))
        {
            // Domain slots cannot be used for spontaneous casting
            return;
        }

        var castingClassCode = GameSystems.Spell.GetCastingClass(spellClassCode);

        if (castingClassCode == Stat.level_cleric)
        {
            var alignmentChoice = obj.GetInt32(obj_f.critter_alignment_choice);
            var spellLevel      = spellData.spellLevel;

            var type = alignmentChoice == 1 ? SpontCastType.GoodCleric : SpontCastType.EvilCleric;
            if (!SpontaneousCastSpells.TryGet(type, spellLevel, out var spontSpellEnum))
            {
                return;
            }

            // NOTE: This clears metamagic (deliberately)
            var d20SpellData = new D20SpellData(spontSpellEnum, spellData.classCode, spellData.spellLevel);

            var radEntry = RadialMenuEntry.CreateSpellAction(d20SpellData, D20ActionType.CAST_SPELL);
            radEntry.d20ActionData1    = 0;
            radEntry.helpSystemHashkey = "TAG_CLASS_FEATURES_CLERIC_SPONTANEOUS_CASTING";

            SetMorphsTo(obj, nodeIdx, AddRootNode(obj, ref radEntry));
        }
        else if (castingClassCode == Stat.level_druid)
        {
            var spellLevel = spellData.spellLevel;
            if (!SpontaneousCastSpells.TryGet(SpontCastType.Druid, spellLevel, out var spontSpellEnum) ||
                !SpontaneousCastSpells.TryGetDruidOptionsKey(spellLevel, out var optionsKey))
            {
                return;
            }

            var radEntry  = RadialMenuEntry.CreateParent(GameSystems.Spell.GetSpellName(spontSpellEnum));
            var parentIdx = AddRootNode(obj, ref radEntry);
            SetMorphsTo(obj, nodeIdx, parentIdx);

            // Now add sub-entries for each summonable creature
            // TODO: Move this functionality into SpellSystem, since it's used in multiple places
            var numSummonsTxt = GameSystems.Spell.GetSpellsRadialMenuOptions(optionsKey);
            var numSummons    = int.Parse(numSummonsTxt);
            for (int i = 1; i <= numSummons; i++)
            {
                var protoNumTxt = GameSystems.Spell.GetSpellsRadialMenuOptions(i + optionsKey);
                var protoNum    = int.Parse(protoNumTxt);
                var protoHandle = GameSystems.Proto.GetProtoById(protoNum);

                var summonSpellData = new D20SpellData(spontSpellEnum, spellData.classCode, spellLevel);
                summonSpellData.spontCastType = SpontCastType.Druid;

                var summonRadEntry = RadialMenuEntry.CreateSpellAction(summonSpellData, D20ActionType.CAST_SPELL);
                summonRadEntry.text           = GameSystems.Description.Get(protoHandle.GetInt32(obj_f.description));
                summonRadEntry.d20ActionData1 = 0; // TODO Check if restting is necessary vs. CreateSpellAction
                SetCallbackCopyEntryToSelected(ref summonRadEntry);
                summonRadEntry.minArg = protoNum;
                summonRadEntry.AddAsChild(obj, parentIdx);
            }
        }
    }
Esempio n. 4
0
    private void AddSpell(GameObject caster, SpellStoreData spData)
    {
        if (spData.spellStoreState.usedUp)
        {
            return;
        }

        if (!GameSystems.Spell.spellCanCast(caster, spData.spellEnum, spData.classCode, spData.spellLevel))
        {
            return;
        }

        if (spData.classCode == SpellSystem.GetSpellClass(Stat.level_paladin) &&
            GameSystems.D20.D20Query(caster, D20DispatcherKey.QUE_IsFallenPaladin))
        {
            return;
        }

        if (!TryGetParentNodeForSpell(caster, spData.classCode, spData.spellLevel, out var specNode))
        {
            return;
        }

        RadialMenuEntry entry;

        if (!GameSystems.Spell.SpellHasMultiSelection(spData.spellEnum))
        {
            var d20SpellData = new D20SpellData(spData.spellEnum, spData.classCode, spData.spellLevel, -1,
                                                spData.metaMagicData);
            entry = RadialMenuEntry.CreateSpellAction(d20SpellData, D20ActionType.CAST_SPELL);
            entry.d20ActionData1 = 0; // TODO: Check the CAST_SPELL action whether this is needed

            var nodeIdx = GameSystems.D20.RadialMenu.AddToStandardNode(caster, ref entry, specNode);
            SetSpontaneousCastingAltNode(caster, nodeIdx, spData);
            return;
        }

        // Multiselection spells section
        entry = RadialMenuEntry.CreateParent(GameSystems.Spell.GetSpellName(spData.spellEnum));

        // the parent node
        var parentNodeIdx = AddParentChildNode(caster, ref entry, GetStandardNode(specNode));

        SetSpontaneousCastingAltNode(caster, parentNodeIdx, spData);

        // the options
        if (!GameSystems.Spell.TryGetMultiSelectOptions(spData.spellEnum, out var multiOptions))
        {
            Logger.Error("Spell multiselect options not found!");
            return;
        }

        // populate options
        for (var i = 0; i < multiOptions.Count; i++)
        {
            var op = multiOptions[i];

            var d20SpellData = new D20SpellData(spData.spellEnum, spData.classCode, spData.spellLevel, -1, spData.metaMagicData);
            var castEntry    = RadialMenuEntry.CreateSpellAction(d20SpellData, D20ActionType.CAST_SPELL);
            castEntry.d20ActionData1 = 0; // TODO: Check if this is really necessary or if it's okay what CreateSpellAction does
            SetCallbackCopyEntryToSelected(ref castEntry);

            if (op.isProto)
            {
                var protoId = op.value;
                entry.minArg = protoId;

                var protoObj = GameSystems.Proto.GetProtoById(protoId);
                entry.text = GameSystems.Description.Get(protoObj.GetInt32(obj_f.description));
            }
            else
            {
                entry.text   = GameSystems.Spell.GetSpellsRadialMenuOptions(op.value);
                entry.minArg = i + 1;
            }

            entry.AddAsChild(caster, parentNodeIdx);
        }
    }