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); }
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; } } }
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); } } }
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); } }