diff --git a/Defs/CustomDefs/TM_CustomClassDef.xml b/Defs/CustomDefs/TM_CustomClassDef.xml
index df0ff495..c5eabf22 100644
--- a/Defs/CustomDefs/TM_CustomClassDef.xml
+++ b/Defs/CustomDefs/TM_CustomClassDef.xml
@@ -39,12 +39,17 @@
+
+
+ TM_C_Firebolt
+
+
true
true
-
-
+
+
false
false
@@ -54,10 +59,32 @@
-
+
+ TM_C_Firebolt
+ 2
+ 1
+
+ TM_Firebolt
+ TM_Firestorm
+
+
+ TM_Custom_detailed_upgrade
+ TM_Custom_minimal_upgrade
+
+
-
+ Detailed upgrade
+ TM_Firebolt_pwr
+
Custom upgrade
+ description of this def
+ Description
+ 2
+ 2
+
+
+ TM_Custom_minimal_upgrade
+
- =============-->
-
\ No newline at end of file
+
diff --git a/Defs/CustomDefs/TM_FieldMedic.xml b/Defs/CustomDefs/TM_FieldMedic.xml
new file mode 100644
index 00000000..961ff652
--- /dev/null
+++ b/Defs/CustomDefs/TM_FieldMedic.xml
@@ -0,0 +1,437 @@
+
+
+
+
+
+
+ TM_Power_Emergency
+ 1
+
+ TM_Emergency
+
+
+
+
+ TM_Emergency
+ Emergency
+ UI/Emergency
+ The Field Medic rushes to aid their patients.
+
+ The Field Medic gains increased base movement speed, tending speed and tending quality as well as reduced pain for a 30 seconds.
+
+ TM_Stamina
+ 30
+
+ true
+ .08
+ 1
+ true
+
+ TorannMagic.Verb_SB
+ false
+ false
+ false
+ false
+ true
+ TM_Emergency
+ false
+ Projectile_Default
+ 0
+ 60.0
+ 0
+ TM_VibrationLow
+ 1
+ false
+ TargetSelf
+
+ true
+ false
+ false
+ false
+
+
+
+ TM_EmergencyHD
+ 1
+ 0.030
+
+
+
+
+
+
+ TM_EmergencyHD
+ HediffWithComps
+ Emergency
+ I'll do my best.
+ (0.24,0.8,0.24)
+ true
+ 1.0
+ false
+
+
+ -1
+
+
+
+
+ I
+ .001
+ -0.2
+
+ 0.4
+ 0.1
+ 0.5
+
+
+
+
+
+
+
+ TM_Power_MedicalSupply
+ 1
+
+ TM_MedicalSupply
+
+
+
+ TM_Upgrade_MedicalSupply_capacity
+ Medical Supply Capacity
+ TM_MedicalSupplyHD_capacity
+ Capacity
+ Increases the maximum amount of supply carried by the Field Medic
+ 1
+ 7
+
+
+
+ TM_MedicalSupply
+ Resupply
+ UI/bloodgift
+ Resupply the Field Medic's medical supplies.
+
+The Field Medic will use one item from target stockpile and convert it into personal medical supplies he can use to fuel his abilities.
+
+The amount of supply gained is equal to 20 times the medical potency of the item squared:
+medicine - 20 medical supplies
+herbal medicine - 4.9 medical supplies
+glitterworld medicine - 51.2 medical supplies
+
+The Field Medic can train to convert his equipment back into common medicine
+ 0
+ true
+ 1
+ TM_MedicalSupplyHD
+ false
+
+ TorannMagic.Verb_MedicalSupply
+ false
+ false
+ false
+ false
+ true
+ TM_MedicalSupply
+ false
+ Projectile_Default
+ 8
+ 1.0
+ 2
+ TM_VibrationLow
+ 10
+ false
+
+ false
+ false
+ false
+ true
+
+
+
+
+ TM_MedicalSupplyHD
+ HediffWithComps
+ Medical Supplies
+ Accounts for the medical supplies equipped by a Field Medic.
+ (0.24,0.8,0.24)
+ false
+ false
+ false
+ false
+ 0.001
+ false
+ 20
+
+
+ 60.0
+ 10.0
+ TM_MedicalSupplyHD_capacity
+ 0.0
+ 0.0
+ medical supplies are not meant to regen
+
+
+
+
+
+ 0
+ true
+
+
+
+
+
+
+ TM_Power_Medigel
+ 1
+
+ TM_Medigel
+
+
+
+
+ TorannMagic.CustomAbility
+ TM_Medigel
+ Medigel
+ UI/Medigel
+ Applies a healing gel to the wounds of a target
+
+The Field Medic applies a gel on the wounds of a target, healing them over time.
+
+ TM_MedicalSupplyHD
+ 20
+
+ true
+ 1
+ false
+
+ TorannMagic.Verb_SB
+ false
+ false
+ false
+ false
+ true
+ TM_Medigel
+ false
+ Projectile_Default
+ 3
+ 1.0
+ 1
+ true
+
+ 1
+ false
+ TargetThing
+
+ true
+ false
+ true
+ false
+
+
+
+ TM_Regeneration
+ 1
+
+
+
+
+
+
+
+ TM_Power_CombatDrugs
+ 1
+
+ TM_CombatDrugs
+
+
+ TM_Upgrade_CombatDrugs_amount
+ TM_Upgrade_CombatDrugs_duration
+
+
+
+ TM_Upgrade_CombatDrugs_duration
+ Potency
+ Increases the number of effects applied.
+
+level 0: 2 effects
+level 1: 3 effects
+level 2: all 4 effects
+ 2
+ 2
+
+
+ TM_Upgrade_CombatDrugs_amount
+ Stabilisation
+ Increases the duration of effects applied.
+
+Lasts 120 seconds plus 20 seconds per upgrade.
+ 1
+ 6
+
+
+
+ TorannMagic.CustomAbility
+ TM_CombatDrugs
+ Combat Drugs
+ UI/bloodgift
+ Gives combat drugs to an ally
+
+The Field Medic throws a syringe of improvised combat drugs to an ally.
+The effects are always positive but may vary.
+
+ TM_MedicalSupplyHD
+ 10
+
+ true
+ 1
+ false
+
+ TorannMagic.Verb_ApplyRandomHediffFromList
+
+ TM_CombatDrugsPain_HD
+ TM_CombatDrugsMoving_HD
+ TM_CombatDrugsAwareness_HD
+ TM_CombatDrugsAccuracy_HD
+
+ 2
+ TM_Upgrade_CombatDrugs_amount
+ 0.12
+
+ TM_Upgrade_CombatDrugs_amount
+ 0.02
+ true
+ false
+ false
+ false
+ false
+ true
+ TM_CombatDrugs
+ false
+ Projectile_Default
+ 3
+ 1.0
+ 10
+ true
+
+ 20
+ false
+ TargetThing
+
+ true
+ false
+ true
+ false
+
+
+
+
+
+ TM_CombatDrugsPain_HD
+ HediffWithComps
+ Combat Drugs (painkiller)
+ Combat drugs reducing pain
+ (0.24,0.8,0.24)
+ true
+ 1.0
+ false
+
+
+ -1
+
+
+
+
+
+ .001
+ 0.2
+
+
+
+
+ TM_CombatDrugsMoving_HD
+ HediffWithComps
+ Combat Drugs (movement)
+ Combat drugs enhancing movement
+ (0.24,0.8,0.24)
+ true
+ 1.0
+ false
+
+
+ -1
+
+
+
+
+
+ .001
+
+
+ Moving
+ .2
+
+
+
+ 0.5
+
+
+
+
+
+ TM_CombatDrugsAwareness_HD
+ HediffWithComps
+ Combat Drugs (awareness)
+ Combat drugs enhancing awareness
+ (0.24,0.8,0.24)
+ true
+ 1.0
+ false
+
+
+ -1
+
+
+
+
+
+ .001
+
+
+ Sight
+ .2
+
+
+
+ +5
+
+
+
+
+
+ TM_CombatDrugsAccuracy_HD
+ HediffWithComps
+ Combat Drugs (accuracy)
+ Combat drugs enhancing accuracy
+ (0.24,0.8,0.24)
+ true
+ 1.0
+ false
+
+
+ -1
+
+
+
+
+
+ .001
+
+ +5
+ +5
+
+
+
+
+
+
diff --git a/Source/TMagic/TMagic/BasePower.cs b/Source/TMagic/TMagic/BasePower.cs
new file mode 100644
index 00000000..b5a9b49b
--- /dev/null
+++ b/Source/TMagic/TMagic/BasePower.cs
@@ -0,0 +1,84 @@
+using AbilityUser;
+using System.Collections.Generic;
+using System.Linq;
+using UnityEngine;
+using Verse;
+
+namespace TorannMagic
+{
+ public abstract class BasePower : IExposable
+ {
+ private List abilityDefs;
+ private List upgrades;
+
+ public int ticksUntilNextCast = -1;
+
+ public int level;
+
+ public int learnCost = 2;
+ public int upgradeCost = 1;
+ public int maxLevel = 3;
+
+ public bool learned = true;
+ public bool autocast = false;
+ protected int interactionTick = 0;
+
+ public bool AutoCast
+ {
+ get => autocast;
+ set
+ {
+ if (interactionTick < Find.TickManager.TicksGame)
+ {
+ autocast = value;
+ interactionTick = Find.TickManager.TicksGame + 30;
+ }
+ }
+ }
+
+ public AbilityDef NextLevelAbilityDef
+ {
+ get => level + 1 < this.Abilities.Count ? Abilities[level + 1] : Abilities.Last();
+ }
+
+ public AbilityDef AbilityDef
+ {
+ get => level < this.Abilities.Count ? Abilities[level] : Abilities.Last();
+ }
+
+ public List Abilities
+ {
+ get => abilityDefs;
+ protected set => abilityDefs = value;
+ }
+
+ public List Upgrades
+ {
+ get => upgrades;
+ protected set => upgrades = value;
+ }
+
+ public Texture2D Icon
+ {
+ get => this.AbilityDef.uiIcon;
+ }
+
+ public BasePower()
+ {
+ abilityDefs = new List();
+ upgrades = new List();
+ }
+
+ public virtual void ExposeData()
+ {
+ Scribe_Values.Look(ref this.learned, "learned", true, false);
+ Scribe_Values.Look(ref this.autocast, "autocast", false, false);
+ Scribe_Values.Look(ref this.learnCost, "learnCost", 2, false);
+ Scribe_Values.Look(ref this.level, "level", 0, false);
+ Scribe_Values.Look(ref this.maxLevel, "maxLevel", 3, false);
+ Scribe_Values.Look(ref this.ticksUntilNextCast, "ticksUntilNextCast", -1, false);
+ Scribe_Collections.Look(ref this.abilityDefs, "abilityDefs", LookMode.Def, null);
+ Scribe_Collections.Look(ref this.upgrades, "upgrades", LookMode.Value, null);
+ }
+ }
+}
diff --git a/Source/TMagic/TMagic/CompAbilityUserCustom.cs b/Source/TMagic/TMagic/CompAbilityUserCustom.cs
new file mode 100644
index 00000000..f354cb33
--- /dev/null
+++ b/Source/TMagic/TMagic/CompAbilityUserCustom.cs
@@ -0,0 +1,463 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Runtime.CompilerServices;
+using RimWorld;
+using UnityEngine;
+using AbilityUser;
+using Verse;
+using Verse.AI;
+using Verse.Sound;
+using AbilityUserAI;
+
+namespace TorannMagic
+{
+ [CompilerGenerated]
+ [Serializable]
+ [StaticConstructorOnStartup]
+ public class CompAbilityUserCustom : CompAbilityUser
+ {
+ public string LabelKey = "TM_Custom";
+
+ public int customIndex = -2;
+ public TMDefs.TM_CustomClass customClass = null;
+
+ public bool temporaryPowersForNonColonists = true;
+
+ private int autocastTick = 0;
+ private int nextAICastAttemptTick = 0;
+ public float weaponDamage = 1;
+
+
+ private CustomData data = null;
+ public CustomData Data
+ {
+ get
+ {
+ bool flag = this.data == null;
+ if (flag)
+ {
+ this.data = new CustomData(customClass?.customPowers?.ConvertAll((PowerDef def) => new CustomPower(def)));
+ }
+ return this.data;
+ }
+ }
+
+ public override void PostDeSpawn(Map map)
+ {
+ base.PostDeSpawn(map);
+ }
+
+ public override void PostDraw()
+ {
+ if (IsInitialized)
+ {
+ ModOptions.SettingsRef settingsRef = new ModOptions.SettingsRef();
+ if (settingsRef.AIFriendlyMarking && base.AbilityUser.IsColonist)
+ {
+ //DrawMageMark();
+ }
+ if (settingsRef.AIMarking && !base.AbilityUser.IsColonist)
+ {
+ //DrawMageMark();
+ }
+
+ //TODO: draw ability-specific things like TechnoBit, DemonScorn wings and MageLight ?
+ }
+
+ base.PostDraw();
+ }
+
+ public override void CompTick()
+ {
+ base.CompTick();
+ bool flag = base.AbilityUser != null;
+ if (flag && IsInitialized)
+ {
+ bool flag4 = Find.TickManager.TicksGame % 30 == 0;
+ if (flag4)
+ {
+ bool flag5 = this.Data.UserXP > this.Data.XPForLevel(Data.UserLevel + 1);
+ if (flag5)
+ {
+ this.LevelUp();
+ }
+ if (Find.TickManager.TicksGame % 60 == 0)
+ {
+ if (this.Pawn.IsColonist && this.temporaryPowersForNonColonists)
+ {
+ ResolveFactionChange();
+ }
+ }
+ }
+ bool spawned = base.AbilityUser.Spawned;
+ if (spawned)
+ {
+ if (Find.TickManager.TicksGame % 60 == 0)
+ {
+ if (this.Pawn.IsColonist)
+ {
+ // TODO: resolve sustained abilities such as power nodes, minions, time mark, etc...
+ }
+ //TODO: update resources like Mana or Blood
+
+ }
+ ModOptions.SettingsRef settingsRef = new ModOptions.SettingsRef();
+ if (this.autocastTick < Find.TickManager.TicksGame) //180 default
+ {
+ //TODO: check for autocast
+ this.autocastTick = Find.TickManager.TicksGame + (int)Rand.Range(.8f * settingsRef.autocastEvaluationFrequency, 1.2f * settingsRef.autocastEvaluationFrequency);
+ }
+ if (!this.Pawn.IsColonist && settingsRef.AICasting && settingsRef.AIAggressiveCasting && Find.TickManager.TicksGame > this.nextAICastAttemptTick) //Aggressive AI Casting
+ {
+ this.nextAICastAttemptTick = Find.TickManager.TicksGame + Rand.Range(300, 500);
+ if (this.Pawn.jobs != null && this.Pawn.CurJobDef != TorannMagicDefOf.TMCastAbilitySelf && this.Pawn.CurJobDef != TorannMagicDefOf.TMCastAbilityVerb)
+ {
+ ResolveEnnemyAutocast();
+ }
+ }
+ //TODO: resolve passive abilities like TechnoOverdrive, Warlock Empathy and Succubus Lovin
+ if (Find.TickManager.TicksGame % 299 == 0) //cache weapon damage for tooltip and damage calculations
+ {
+ this.weaponDamage = TM_Calc.GetSkillDamage(this.Pawn);
+ }
+ }
+ else
+ {
+ // update cooldowns while not on a map
+ if (Find.TickManager.TicksGame % 600 == 0)
+ {
+ if (this.Pawn.Map == null)
+ {
+ if (AbilityData?.AllPowers != null)
+ {
+ foreach (PawnAbility power in AbilityData.AllPowers)
+ {
+ int cd = power.CooldownTicksLeft;
+ cd = cd < 600 ? cd : 600;
+ power.CooldownTicksLeft -= cd;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ public override void PostSpawnSetup(bool respawningAfterLoad)
+ {
+ base.PostSpawnSetup(respawningAfterLoad);
+ bool flag = base.AbilityUser != null;
+ //Log.Message("Post Spawn Setup for " + this.AbilityUser?.Name + " init status:" + IsInitialized);
+ if (flag && IsInitialized)
+ {
+ this.ResolveInspectorTab();
+ bool spawned = base.AbilityUser.Spawned;
+ if (spawned)
+ {
+ bool flag2 = base.AbilityUser.story != null;
+ if (flag2)
+ {
+ this.ResolveOngoingAbilities();
+ }
+ }
+ }
+ }
+
+ public override bool TryTransformPawn()
+ {
+ return TM_ClassUtility.IsCustomClassIndex(this.AbilityUser.story.traits.allTraits) >= 0;
+ }
+
+ public override void Initialize()
+ {
+ //Log.Message("Initializing " + this.AbilityUser.Name);
+ if (this.customClass == null)
+ {
+ this.customIndex = TM_ClassUtility.IsCustomClassIndex(this.AbilityUser.story.traits.allTraits);
+ //Log.Message("Initializing " + this.AbilityUser.Name + " with class " + this.customIndex);
+ if (this.customIndex >= 0)
+ {
+ this.customClass = TM_ClassUtility.CustomClasses()[this.customIndex];
+ //Log.Message("This Custom class has " + customClass.customPowers.Count + " powers " + this.customIndex);
+ this.data = new CustomData(customClass.customPowers?.ConvertAll((PowerDef def) => new CustomPower(def)));
+ base.Initialize();
+ }
+ //else
+ //{
+ // this.parent.AllComps.Remove(this);
+ //}
+ }
+ }
+
+ public override void PostInitialize()
+ {
+ base.PostInitialize();
+ this.temporaryPowersForNonColonists = !this.Pawn.IsColonist;
+ AssignAbilities(temporaryPowersForNonColonists); // non-colonists have access to all starting abilities.
+ ResolveInspectorTab();
+ }
+
+ private void ResolveOngoingAbilities()
+ {
+ // TODO: resolve ongoing abilities that have fleeting data, such as druid's Fertile Lands
+ }
+
+ public void DrawMageMark()
+ {
+ if (this.customClass != null)
+ {
+ Material mat = TM_RenderQueue.mageMarkMat;
+ if (this.customClass.classIconPath != "")
+ {
+ mat = MaterialPool.MatFrom("Other/" + this.customClass.classIconPath.ToString());
+ }
+ if (this.customClass.classIconColor != null)
+ {
+ mat.color = this.customClass.classIconColor;
+ }
+ float num = Mathf.Lerp(1.2f, 1.55f, 1f);
+ Vector3 vector = this.AbilityUser.Drawer.DrawPos;
+ vector.x = vector.x + .45f;
+ vector.z = vector.z + .45f;
+ vector.y = Altitudes.AltitudeFor(AltitudeLayer.MoteOverhead);
+ float angle = 0f;
+ Vector3 s = new Vector3(.28f, 1f, .28f);
+ Matrix4x4 matrix = default(Matrix4x4);
+ matrix.SetTRS(vector, Quaternion.AngleAxis(angle, Vector3.up), s);
+ Graphics.DrawMesh(MeshPool.plane10, matrix, mat, 0);
+ }
+ }
+
+ public void ResolveFactionChange()
+ {
+ if (temporaryPowersForNonColonists)
+ {
+ RemovePowers();
+ AssignAbilities();
+ this.temporaryPowersForNonColonists = false;
+ }
+ }
+
+ public void LevelUp(bool hideNotification = false)
+ {
+ if (!(this.Pawn.story.traits.HasTrait(TorannMagicDefOf.Faceless) || this.Pawn.story.traits.HasTrait(TorannMagicDefOf.TM_Wayfarer)))
+ {
+ if (this.Data.UserLevel < 150)//customClass.maxLevel)
+ {
+ this.Data.UserLevel++;
+ bool flag = !hideNotification;
+ if (flag)
+ {
+ ModOptions.SettingsRef settingsRef = new ModOptions.SettingsRef();
+ if (Pawn.IsColonist && settingsRef.showLevelUpMessage)
+ {
+ Messages.Message( this.Pawn.Name + " increased his skill level in " + this.customClass.classTrait.label, //TranslatorFormattedStringExtensions.Translate("TM_MagicLevelUp",this.parent.Label),
+ this.Pawn, MessageTypeDefOf.PositiveEvent);
+ }
+ }
+ }
+ else
+ {
+ this.Data.UserXP = this.Data.XPForLevel(this.Data.UserLevel);
+ }
+ }
+ }
+
+ public void LevelUpPower(BasePower power)
+ {
+ int savedTicks = this.AbilityData.Powers.Find((PawnAbility a) => a.Def == power.AbilityDef)?.CooldownTicksLeft ?? -1;
+ base.RemovePawnAbility(power.AbilityDef);
+ power.level++;
+ //if ((power.AbilityDef as TMAbilityDef)?.shouldInitialize ?? false)
+ //{ //TODO: handle passive upgrades. shouldInitialize is not reliable because upgraded spells have it set to false, it is used as "isInitialAbility".
+ base.AddPawnAbility(power.AbilityDef, true, savedTicks);
+ Log.Message(power.AbilityDef.defName);
+ //}
+ }
+
+ private void ResolveEnnemyAutocast()
+ {
+ IEnumerable enumerable = this.Pawn.EligibleAIProfiles();
+ if (enumerable != null && enumerable.Count() > 0)
+ {
+ foreach (AbilityUserAIProfileDef item in enumerable)
+ {
+ if (item != null)
+ {
+ AbilityAIDef useThisAbility = null;
+ if (item.decisionTree != null)
+ {
+ useThisAbility = item.decisionTree.RecursivelyGetAbility(this.Pawn);
+ }
+ if (useThisAbility != null)
+ {
+ ThingComp val = this.Pawn.AllComps.First((ThingComp comp) => ((object)comp).GetType() == item.compAbilityUserClass);
+ CompAbilityUser compAbilityUser = val as CompAbilityUser;
+ if (compAbilityUser != null)
+ {
+ PawnAbility pawnAbility = compAbilityUser.AbilityData.AllPowers.First((PawnAbility ability) => ability.Def == useThisAbility.ability);
+ string reason = "";
+ if (pawnAbility.CanCastPowerCheck(AbilityContext.AI, out reason))
+ {
+ LocalTargetInfo target = useThisAbility.Worker.TargetAbilityFor(useThisAbility, this.Pawn);
+ if (target.IsValid)
+ {
+ pawnAbility.UseAbility(AbilityContext.Player, target);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private void AssignAbilities(bool learnAll = false)
+ {
+ Pawn abilityUser = base.AbilityUser;
+ for (int z = 0; z < this.Data.Powers.Count; z++)
+ {
+ BasePower p = this.Data.Powers[z];
+ if (p.AbilityDef is TMAbilityDef)
+ {
+ TMAbilityDef ability = (TMAbilityDef)p.AbilityDef;
+ // TODO: learning method : scrolls, chance, Inspiration?
+ if (!(learnAll || !abilityUser.health.hediffSet.HasHediff(TorannMagicDefOf.TM_Uncertainty, false) || Rand.Chance(ability.learnChance)))
+ {
+ p.learned = false;
+ }
+
+ if (p.learned)// && ability.shouldInitialize)
+ { //TODO: handle passive abilities. shouldInitialize is not reliable because upgraded spells have it set to false, it is used as "isInitialAbility".
+ this.AddPawnAbility(ability);
+ }
+ }
+ }
+ if (this.customClass.classHediff != null)
+ {
+ HealthUtility.AdjustSeverity(abilityUser, this.customClass.classHediff, this.customClass.hediffSeverity);
+ }
+ }
+
+ public void RemovePowers()
+ {
+ for (int i = 0; i < this.Data.Powers.Count; i++)
+ {
+ BasePower mp = this.Data.Powers[i];
+ for (int j = 0; j < mp.Abilities.Count; j++)
+ {
+ this.RemovePawnAbility(mp.Abilities[j]);
+ }
+ mp.learned = false;
+ }
+ }
+
+ public void ResetSkills()
+ {
+ int skillpoints = this.Data.Upgrades.Values.Sum((MagicPowerSkill x) => x.level * x.costToLevel);
+
+ int magicPts = this.Data.AbilityPoints;
+
+ this.data.ResetAllSkills();
+
+ this.Data.AbilityPoints = magicPts + skillpoints;
+ }
+
+ public void RemoveTraits()
+ {
+ List traits = this.AbilityUser.story.traits.allTraits;
+ for (int i = 0; i < traits.Count; i++)
+ {
+ if (this.customClass != null)
+ {
+ traits.Remove(this.AbilityUser.story.traits.GetTrait(this.customClass.classTrait));
+ this.customClass = null;
+ this.customIndex = -2;
+ }
+ }
+ }
+
+ public void RemoveTMagicHediffs()
+ {
+ // TODO
+ List allHediffs = this.Pawn.health.hediffSet.GetHediffs().ToList();
+ for (int i = 0; i < allHediffs.Count(); i++)
+ {
+ Hediff hediff = allHediffs[i];
+ if (hediff.def.defName.StartsWith("TM_"))
+ {
+ this.Pawn.health.RemoveHediff(hediff);
+ }
+
+ }
+ }
+
+ public void RemoveAbilityUser()
+ {
+ this.RemovePowers();
+ this.RemoveTMagicHediffs();
+ this.RemoveTraits();
+ this.data = new CustomData();
+ base.IsInitialized = false;
+ }
+
+ //public override List IgnoredHediffs()
+ //{
+ // return new List
+ // {
+ // TorannMagicDefOf.TM_MightUserHD,
+ // TorannMagicDefOf.TM_MagicUserHD
+ // };
+ //}
+
+ public override void PostPreApplyDamage(DamageInfo dinfo, out bool absorbed)
+ {
+ //TODO: move the damage absorbtion code for each abilities in their own hediff
+ base.PostPreApplyDamage(dinfo, out absorbed);
+ }
+
+ public void ResolveInspectorTab()
+ {
+ InspectTabBase inspectTabsx = base.AbilityUser.GetInspectTabs().FirstOrDefault((InspectTabBase x) => x.labelKey == "TM_TabCustom");
+ IEnumerable inspectTabs = base.AbilityUser.GetInspectTabs();
+ bool flag = inspectTabs != null && inspectTabs.Count() > 0;
+ if (flag)
+ {
+ if (inspectTabsx == null)
+ {
+ try
+ {
+ base.AbilityUser.def.inspectorTabsResolved.Add(InspectTabManager.GetSharedInstance(typeof(ITab_Pawn_Custom)));
+ }
+ catch (Exception ex)
+ {
+ Log.Error(string.Concat(new object[]
+ {
+ "Could not instantiate inspector tab of type ",
+ typeof(ITab_Pawn_Custom),
+ ": ",
+ ex
+ }));
+ }
+ }
+ }
+ }
+
+ public override void PostExposeData()
+ {
+ base.PostExposeData();
+ Scribe_Values.Look(ref this.temporaryPowersForNonColonists, "temporaryPowersForNonColonists", true, false);
+ Scribe_Deep.Look(ref this.data, "customClassData", new object[] { });
+ bool flag11 = Scribe.mode == LoadSaveMode.PostLoadInit;
+ if (flag11)
+ {
+ if (this.customIndex >= 0)
+ {
+ this.customClass = TM_ClassUtility.CustomClasses()[this.customIndex];
+ }
+ }
+ }
+
+ }
+}
diff --git a/Source/TMagic/TMagic/CustomAbility.cs b/Source/TMagic/TMagic/CustomAbility.cs
new file mode 100644
index 00000000..e7e66ba7
--- /dev/null
+++ b/Source/TMagic/TMagic/CustomAbility.cs
@@ -0,0 +1,213 @@
+using AbilityUser;
+using RimWorld;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using Verse;
+using UnityEngine;
+
+namespace TorannMagic
+{
+ public class CustomAbility : PawnAbility
+ {
+
+ public TMAbilityDef TMDef
+ {
+ get
+ {
+ return base.Def as TMAbilityDef;
+ }
+ }
+ TMAbilityCost cost;
+ TMAbilityCost Cost
+ {
+ get
+ {
+ if (cost == null) // TODO: handle when the Cost cannot be created
+ {
+ cost = TMDef.customCost?.ForPawn(this.Pawn)
+ ?? (TMDef.manaCost > 0 ? new TMAbilityNeedCost(this.Pawn.needs.TryGetNeed(TorannMagicDefOf.TM_Mana), TMDef.manaCost * 100f)
+ : (TMDef.staminaCost > 0 ? new TMAbilityNeedCost(this.Pawn.needs.TryGetNeed(TorannMagicDefOf.TM_Stamina), TMDef.staminaCost * 100f)
+ : new TMAbilityBadCost() as TMAbilityCost));
+ }
+ return cost;
+ }
+ }
+ public CustomAbility()
+ {
+ }
+
+ public CustomAbility(AbilityData data) : base(data)
+ {
+ }
+
+ public CustomAbility(CompAbilityUser abilityUser) : base(abilityUser)
+ {
+ this.abilityUser = (abilityUser as CompAbilityUserMagic);
+ }
+
+ public CustomAbility(Pawn user, AbilityUser.AbilityDef pdef) : base(user, pdef)
+ {
+ this.cost = (pdef as TMAbilityDef)?.customCost.ForPawn(user);
+ }
+
+ public override void PostAbilityAttempt() //commented out in CompAbilityUserMagic
+ {
+ base.PostAbilityAttempt();
+
+ ModOptions.SettingsRef settingsRef = new ModOptions.SettingsRef();
+ if (!this.Pawn.IsColonist && settingsRef.AIAggressiveCasting)// for AI
+ {
+ this.CooldownTicksLeft = Mathf.RoundToInt(this.MaxCastingTicks/2f);
+ }
+ else
+ {
+ this.CooldownTicksLeft = Mathf.RoundToInt(this.MaxCastingTicks); //TODO: integrate cooldown reduction effects, Arcalleum
+ }
+ float cost;
+ Cost.PayCost(out cost);
+ CompAbilityUserCustom custom = this.AbilityUser.Pawn.TryGetComp();
+ if (custom != null && custom.Data.ReturnMatchingPower(this.Def, true)?.learned == true)
+ {
+ custom.Data.UserXP += Mathf.RoundToInt(cost * settingsRef.xpMultiplier); // TODO: XP modifiers (custom.Data.GainXP ? )
+ }
+ }
+
+ public override string PostAbilityVerbCompDesc(VerbProperties_Ability verbDef)
+ {
+ return "Costs " + Cost.Description;
+ //CompAbilityUserCustom custom = this.AbilityUser.Pawn.TryGetComp();
+ //BasePower power = custom.Data.ReturnMatchingPower(Def);
+ //StringBuilder stringBuilder = new StringBuilder();
+ //TMAbilityDef magicAbilityDef = (TMAbilityDef)verbDef.abilityDef;
+ //bool flag = magicAbilityDef != null;
+ //if (flag)
+ //{
+ // string text = "";
+ // string text2 = "";
+ // string text3 = "";
+ // float num = 0;
+ // float num2 = 0;
+ //if (magicAbilityDef == TorannMagicDefOf.TM_Teleport)
+ //{
+ // num = this.MagicUser.ActualManaCost(magicDef)*100;
+ // MagicPowerSkill mps2 = this.MagicUser.MagicData.MagicPowerSkill_Teleport.FirstOrDefault((MagicPowerSkill x) => x.label == "TM_Teleport_ver");
+ // MagicPowerSkill mps1 = this.MagicUser.MagicData.MagicPowerSkill_Teleport.FirstOrDefault((MagicPowerSkill x) => x.label == "TM_Teleport_pwr");
+ // num2 = 80 + (mps1.level * 20) + (mps2.level * 20);
+ // text2 = "TM_AbilityDescPortalTime".Translate(
+ // num2.ToString()
+ // );
+ //}
+ //else
+ //{
+ // num = this.MagicUser.ActualManaCost(magicDef) * 100;
+ //}
+
+ //if (this.Pawn.story.traits.HasTrait(TorannMagicDefOf.Faceless))
+ //{
+ // text = "TM_AbilityDescBaseStaminaCost".Translate(
+ // (magicAbilityDef.manaCost * 100).ToString("n1")
+ // ) + "\n" + "TM_AbilityDescAdjustedStaminaCost".Translate(
+ // (magicDef.manaCost * 100).ToString("n1")
+ // );
+ //}
+ //else
+ //{
+ // text = "TM_AbilityDescBaseManaCost".Translate(
+ // (magicAbilityDef.manaCost * 100).ToString("n1")
+ // ) + "\n" + "TM_AbilityDescAdjustedManaCost".Translate(
+ // num.ToString("n1")
+ // );
+ //}
+ //if(this.MagicUser.coolDown != 1f)
+ //{
+ // text3 = "TM_AdjustedCooldown".Translate(
+ // ((this.MaxCastingTicks * this.MagicUser.coolDown) / 60).ToString("0.00")
+ // );
+ //}
+ //bool flag4 = text3 != "";
+ //if(flag4)
+ //{
+ // stringBuilder.AppendLine(text3);
+ //}
+ //}
+ //return stringBuilder.ToString();
+ }
+
+ public override bool CanCastPowerCheck(AbilityContext context, out string reason)
+ {
+ bool flag = base.CanCastPowerCheck(context, out reason);
+ bool result;
+ if (flag)
+ {
+ reason = "";
+ TMAbilityDef tmAbilityDef;
+ bool flag1 = base.Def != null && (tmAbilityDef = (base.Def as TMAbilityDef)) != null;
+ if (flag1)
+ {
+ float actualCost;
+ if (Cost.CanPayCost(out actualCost))
+ {
+ bool flagMute = this.Pawn.health.hediffSet.HasHediff(TorannMagicDefOf.TM_MuteHD);
+ if(flagMute)
+ {
+ reason = "TM_CasterMute".Translate(
+ base.Pawn.LabelShort
+ );
+ result = false;
+ return result;
+ }
+ }
+ else if (this.Pawn.story.traits.HasTrait(TorannMagicDefOf.Faceless))
+ {
+ CompAbilityUserMight mightComp = this.Pawn.GetComp();
+ bool flag7 = mightComp != null && mightComp.Stamina != null && actualCost < mightComp.Stamina.CurLevel;
+ if (flag7)
+ {
+ reason = "TM_NotEnoughStamina".Translate(
+ base.Pawn.LabelShort
+ );
+ result = false;
+ return result;
+ }
+ }
+ else
+ {
+ reason = "Cannot pay " + Cost.Description;
+ return false;
+ }
+ }
+ //List wornApparel = base.Pawn.apparel.WornApparel;
+ //for (int i = 0; i < wornApparel.Count; i++)
+ //{
+ // if (!wornApparel[i].AllowVerbCast(base.Pawn.Position, base.Pawn.Map, base.abilityUser.Pawn.TargetCurrentlyAimingAt, this.Verb) &&
+ // (this.magicDef.defName == "TM_LightningCloud" || this.magicDef.defName == "Laser_LightningBolt" || this.magicDef.defName == "TM_LightningStorm" || this.magicDef.defName == "TM_EyeOfTheStorm" ||
+ // this.magicDef.defName.Contains("Laser_FrostRay") || this.magicDef.defName == "TM_Blizzard" || this.magicDef.defName == "TM_Snowball" || this.magicDef.defName == "TM_Icebolt" ||
+ // this.magicDef.defName == "TM_Firestorm" || this.magicDef.defName == "TM_Fireball" || this.magicDef.defName == "TM_Fireclaw" || this.magicDef.defName == "TM_Firebolt" ||
+ // this.magicDef.defName.Contains("TM_MagicMissile") ||
+ // this.magicDef.defName.Contains("TM_DeathBolt") ||
+ // this.magicDef.defName.Contains("TM_ShadowBolt") ||
+ // this.magicDef.defName == "TM_BloodForBlood" || this.magicDef.defName == "TM_IgniteBlood" ||
+ // this.magicDef.defName == "TM_Poison" ||
+ // this.magicDef == TorannMagicDefOf.TM_ArcaneBolt) )
+ // {
+ // reason = "TM_ShieldBlockingPowers".Translate(
+ // base.Pawn.Label,
+ // wornApparel[i].Label
+ // );
+ // return false;
+ // }
+ //}
+ result = true;
+
+ }
+ else
+ {
+ result = false;
+ }
+ return result;
+
+ }
+ }
+}
diff --git a/Source/TMagic/TMagic/CustomData.cs b/Source/TMagic/TMagic/CustomData.cs
new file mode 100644
index 00000000..4c4dc82b
--- /dev/null
+++ b/Source/TMagic/TMagic/CustomData.cs
@@ -0,0 +1,153 @@
+using AbilityUser;
+using RimWorld;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Verse;
+
+namespace TorannMagic
+{
+ public class CustomData : IExposable
+ {
+ private int userLevel = 0;
+ private int abilityPoints = 0;
+ private int userXP = 1;
+ private int ticksToLearnXP = -1;
+ private int ticksAffiliation = 0;
+
+ private List powers;
+ private Dictionary upgrades;
+
+ public List Powers
+ {
+ get => this.powers.ConvertAll((CustomPower p) => p as BasePower);
+ }
+
+ public Dictionary Upgrades
+ {
+ get => upgrades;
+ }
+
+ public int UserLevel
+ {
+ get => this.userLevel;
+ set
+ {
+ if (value > this.userLevel)
+ {
+ this.AbilityPoints += value - this.userLevel;
+ }
+ int minXp = XPForLevel(value);
+ if (this.userXP < minXp)
+ {
+ this.userXP = XPForLevel(value);
+ }
+ this.userLevel = value;
+ }
+ }
+
+ public int UserXP
+ {
+ get => this.userXP;
+ set => this.userXP = value;
+ }
+
+ public virtual int XPForLevel(int level)
+ {
+ return level * 500;
+ }
+
+ public int AbilityPoints
+ {
+ get => this.abilityPoints;
+ set => this.abilityPoints = value;
+ }
+
+ public int TicksToLearnXP
+ {
+ get => this.ticksToLearnXP;
+ set => this.ticksToLearnXP = value;
+ }
+
+ public int TicksAffiliation
+ {
+ get => this.ticksAffiliation;
+ set => this.ticksAffiliation = value;
+ }
+
+ public int GetUniquePowersWithSkillsCount()
+ {
+ return this.powers.Count;
+ }
+
+ public int GetUpgradeLevel(string upgradeLabel)
+ {
+ return Upgrades.TryGetValue(upgradeLabel)?.level ?? 0; //FIXME: when not found should we return -1 ?
+ }
+
+ public BasePower ReturnMatchingPower(AbilityUser.AbilityDef def, bool isCurrentAbility = false)
+ {
+ if (def == TorannMagicDefOf.TM_SoulBond || def == TorannMagicDefOf.TM_ShadowBolt || def == TorannMagicDefOf.TM_Dominate)
+ {
+ return null;
+ }
+ return powers.Find((CustomPower p) => p.Abilities.Contains(def) && (!isCurrentAbility || p.AbilityDef == def));
+ }
+
+ public List AllPowersForChaosMage
+ {
+ get
+ {
+ List tmpList = new List();
+ List list = new List();
+ list.Clear();
+ list.AddRange(this.powers.FindAll((CustomPower p) =>
+ {
+ TMAbilityDef def = p.AbilityDef as TMAbilityDef;
+ return def != null && def.canCopy
+ && def.staminaCost < float.Epsilon && def.chiCost < float.Epsilon && def.bloodCost < float.Epsilon;
+ }));
+ return list.ConvertAll((CustomPower p) => p as BasePower); ;
+ }
+ }
+
+ public void ResetAllSkills()
+ {
+ this.Upgrades.Values.ToList().ForEach((MagicPowerSkill m) => m.level = 0);
+ }
+
+ public CustomData()
+ {
+ }
+
+ public CustomData(List powers)
+ {
+ this.userLevel = 0;
+ this.userXP = 0;
+ this.abilityPoints = 0;
+ this.powers = powers ?? new List();
+ this.upgrades = this.powers.SelectMany((CustomPower p) =>
+ p.localDef.upgrades.ConvertAll((UpgradeDef u) => u.asMagicPowerSkill())
+ ).ToDictionary((MagicPowerSkill s) => s.label);
+ //this.upgrades_labels = upgrades.Keys.ToList();
+ //this.upgrades_values = upgrades.Values.ToList();
+ Log.Message("Creating new Data with " + this.powers.Count + " powers and " + this.upgrades.Count + " upgrades");
+ }
+
+ public void ExposeData()
+ {
+ //Scribe_References.Look(ref this.pawn, "magicPawn", false);
+ Scribe_Values.Look(ref this.userLevel, "c_userLevel", 0, false);
+ Scribe_Values.Look(ref this.userXP, "c_userXP", 0, false);
+ Scribe_Values.Look(ref this.abilityPoints, "c_magicAbilityPoints", 0, false);
+ Scribe_Values.Look(ref this.ticksToLearnXP, "c_ticksToLearnMagicXP", -1, false);
+ Scribe_Values.Look(ref this.ticksAffiliation, "c_ticksAffiliation", -1, false);
+ Scribe_Collections.Look(ref this.powers, "c_powers", LookMode.Deep, new object[0]);
+
+ //this.upgrades_labels = upgrades.Keys.ToList();
+ //this.upgrades_values = upgrades.Values.ToList();
+ Scribe_Collections.Look(ref this.upgrades, "c_upgrades", LookMode.Value, LookMode.Deep);
+ //LookMode.Value, LookMode.Deep, ref this.upgrades_labels, ref this.upgrades_values);
+ }
+ }
+}
diff --git a/Source/TMagic/TMagic/CustomPower.cs b/Source/TMagic/TMagic/CustomPower.cs
new file mode 100644
index 00000000..b163429d
--- /dev/null
+++ b/Source/TMagic/TMagic/CustomPower.cs
@@ -0,0 +1,78 @@
+using AbilityUser;
+using System.Collections.Generic;
+using System.Linq;
+using UnityEngine;
+using Verse;
+
+namespace TorannMagic
+{
+ public class CustomPower : BasePower
+ {
+ public PowerDef localDef;
+ public CustomPower()
+ {
+ }
+ public CustomPower(PowerDef def) : base()
+ {
+ this.localDef = def;
+ updateBasePower();
+ }
+ public override void ExposeData()
+ {
+ base.ExposeData();
+ Scribe_Defs.Look(ref localDef, "powerDef");
+ if (Scribe.mode == LoadSaveMode.PostLoadInit)
+ {
+ updateBasePower();
+ }
+ }
+ private void updateBasePower()
+ {
+ this.Abilities = localDef.abilityLine.ConvertAll((TMAbilityDef a) => a as AbilityDef);
+ this.Upgrades = localDef.upgrades.ConvertAll((UpgradeDef u) => u.UpgradeId);
+ this.upgradeCost = localDef.upgradeCost;
+ this.learnCost = localDef.learnCost;
+ this.maxLevel = localDef.abilityLine.Count - 1;
+ }
+ }
+
+ public class PowerDef : Def
+ {
+ public int upgradeCost = 1;
+ public int learnCost = 1;
+ public List abilityLine;
+ public List upgrades = new List();
+ }
+
+ public class UpgradeDef : Def
+ {
+ public string title = null;
+ public string upgradeId = null;
+ public int upgradeCost = 1;
+ public string upgradeDescription = null;
+ public int levelMax = 3;
+
+ public string Title
+ {
+ get => title ?? this.label ?? upgradeId?? this.defName;
+ }
+
+ public string UpgradeId
+ {
+ get => upgradeId ?? this.defName;
+ }
+
+ public string Description
+ {
+ get => upgradeDescription ?? this.description ?? title ?? this.label ?? this.defName;
+ }
+
+ public MagicPowerSkill asMagicPowerSkill()
+ {
+ MagicPowerSkill mps = new MagicPowerSkill(UpgradeId, Description);
+ mps.costToLevel = upgradeCost;
+ mps.levelMax = levelMax;
+ return mps;
+ }
+ }
+}
diff --git a/Source/TMagic/TMagic/HediffComp_AbilityResource.cs b/Source/TMagic/TMagic/HediffComp_AbilityResource.cs
new file mode 100644
index 00000000..f2f60ebb
--- /dev/null
+++ b/Source/TMagic/TMagic/HediffComp_AbilityResource.cs
@@ -0,0 +1,87 @@
+using RimWorld;
+using Verse;
+using UnityEngine;
+using System.Collections.Generic;
+using System;
+using HarmonyLib;
+
+namespace TorannMagic
+{
+ public class HediffCompProperties_AbilityResource : HediffCompProperties
+ {
+ public float maximumBase = 100f;
+ public float maximumPerUpgrade = 0f;
+ public string maximumUpgradeName;
+ public float regenPerTickBase = 0f;
+ public float regenPerTickPerUpgrade = 0f;
+ public string regenPerTickUpgradeName;
+
+ public HediffCompProperties_AbilityResource()
+ {
+ this.compClass = typeof(HediffComp_AbilityResource);
+ }
+
+ }
+
+ [StaticConstructorOnStartup]
+ public class HediffComp_AbilityResource : HediffComp
+ {
+
+ private bool initialized = false;
+ private bool removeNow = false;
+
+ private int eventFrequency = 60;
+
+ private HediffCompProperties_AbilityResource Props { get => this.props as HediffCompProperties_AbilityResource; }
+
+ private string maximumCachedUpgradeName;
+ private float maximumCached;
+ private string regenPerTickCachedUpgradeName;
+ private float regenPerTickCached;
+
+ public override string CompLabelInBracketsExtra => string.Concat(this.parent.Severity.ToString("0."), "/", maximumCached.ToString("0."));
+ public override bool CompShouldRemove => this.removeNow || base.CompShouldRemove;
+
+ private void Initialize()
+ {
+ maximumCachedUpgradeName = Props.maximumUpgradeName ?? (this.parent.def.defName + "_capacity");
+ regenPerTickCachedUpgradeName = Props.regenPerTickUpgradeName ?? (this.parent.def.defName + "_regen");
+ UpdateCachedValues();
+ this.initialized = true;
+ }
+
+ private void UpdateCachedValues()
+ {
+ CustomData customData = this.Pawn.GetComp()?.Data;
+ if (customData != null)
+ {
+ maximumCached = Props.maximumBase + Props.maximumPerUpgrade * customData.GetUpgradeLevel(maximumCachedUpgradeName);
+ regenPerTickCached = Props.regenPerTickBase + Props.regenPerTickPerUpgrade * customData.GetUpgradeLevel(regenPerTickCachedUpgradeName);
+ }
+ }
+
+ public override void CompPostTick(ref float severityAdjustment)
+ {
+ base.CompPostTick(ref severityAdjustment);
+ bool flag = base.Pawn != null && base.Pawn.Map != null;
+ if (flag)
+ {
+ if (!this.initialized)
+ {
+ Initialize();
+ }
+
+ if (Find.TickManager.TicksGame % (this.eventFrequency * 5) == 3)
+ {
+ this.UpdateCachedValues();
+ }
+
+ if (Find.TickManager.TicksGame % this.eventFrequency == 3)
+ {
+ float newValue = this.parent.Severity + eventFrequency * regenPerTickCached;
+ this.parent.Severity = Mathf.Clamp(newValue, 0f, maximumCached);
+ }
+ }
+ }
+ }
+}
diff --git a/Source/TMagic/TMagic/ITab_Pawn_Custom.cs b/Source/TMagic/TMagic/ITab_Pawn_Custom.cs
new file mode 100644
index 00000000..cca8eb2c
--- /dev/null
+++ b/Source/TMagic/TMagic/ITab_Pawn_Custom.cs
@@ -0,0 +1,382 @@
+using HarmonyLib;
+using RimWorld;
+using System;
+using UnityEngine;
+using Verse;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace TorannMagic
+{
+ [StaticConstructorOnStartup]
+ public class ITab_Pawn_Custom : ITab //code by Jecrell
+ {
+ private static readonly Texture2D manaTex = SolidColorMaterials.NewSolidColorTexture(new Color(0.0f, 0.5f, 1f));
+ private Pawn PawnToShowInfoAbout
+ {
+ get
+ {
+ Pawn pawn = base.SelPawn ?? (base.SelThing as Corpse)?.InnerPawn;
+ bool flag3 = pawn == null;
+ if (pawn == null)
+ {
+ Log.Error("Character tab found no selected pawn to display.");
+ }
+ return pawn;
+ }
+ }
+
+ public override bool IsVisible
+ {
+ get
+ {
+ bool flag = base.SelPawn.story != null && base.SelPawn.IsColonist;
+ if (flag)
+ {
+ CompAbilityUserCustom compMagic = base.SelPawn.TryGetComp();
+ return compMagic?.IsInitialized ?? false;
+ }
+ return false;
+ }
+ }
+
+ public ITab_Pawn_Custom()
+ {
+ this.size = MagicCardUtility.MagicCardSize + new Vector2(17f, 17f) * 2f;
+ this.labelKey = "TM_TabCustom";
+ }
+
+ protected override void FillTab()
+ {
+ Rect rect = new Rect(17f, 17f, MagicCardUtility.MagicCardSize.x, MagicCardUtility.MagicCardSize.y);
+ DrawMagicCard(rect, this.PawnToShowInfoAbout);
+ }
+
+ public static void DrawMagicCard(Rect rect, Pawn pawn)
+ {
+
+ CompAbilityUserCustom comp = pawn.GetComp();
+ int sizeY = 0;
+ if (comp.customClass != null)
+ {
+ sizeY = (comp.Data.GetUniquePowersWithSkillsCount() * 80);
+ if (sizeY > 500)
+ {
+ sizeY -= 500;
+ }
+ else
+ {
+ sizeY = 0;
+ }
+ }
+
+ Rect sRect = new Rect(rect.x, rect.y, rect.width - 36f, rect.height + 56f + sizeY);
+ //scrollPosition = GUI.BeginScrollView(rect, scrollPosition, sRect, false, true);
+ GUI.BeginScrollView(rect, Vector2.zero, sRect, false, true);
+
+ bool flag = comp != null;
+ if (flag)
+ {
+ bool flag2 = true; //comp.MagicUserLevel > 0;
+ if (flag2)
+ {
+ float x = Text.CalcSize("TM_Header".Translate()).x;
+ Rect rect2 = new Rect(rect.width / 2f - (x / 2), rect.y, rect.width, MagicCardUtility.HeaderSize);
+ Text.Font = GameFont.Small;
+ Widgets.Label(rect2, "TM_Header".Translate().CapitalizeFirst());
+ Text.Font = GameFont.Small;
+ Widgets.DrawLineHorizontal(rect.x - 10f, rect2.yMax, rect.width - 15f);
+ Rect rect9 = new Rect(rect.x, rect2.yMax + MagicCardUtility.Padding, rect2.width, MagicCardUtility.SkillsColumnHeight);
+ Rect inRect = new Rect(rect9.x, rect9.y + MagicCardUtility.Padding, MagicCardUtility.SkillsColumnDivider, MagicCardUtility.SkillsColumnHeight);
+ Rect inRect2 = new Rect(rect9.x + MagicCardUtility.SkillsColumnDivider, rect9.y + MagicCardUtility.Padding, rect9.width - MagicCardUtility.SkillsColumnDivider, MagicCardUtility.SkillsColumnHeight);
+ InfoPane(inRect, comp, pawn);
+ float x5 = Text.CalcSize("TM_Spells".Translate()).x;
+ Rect rect10 = new Rect(rect.width / 2f - x5 / 2f, rect9.yMax - 60f, rect.width, MagicCardUtility.HeaderSize);
+ Text.Font = GameFont.Small;
+ Widgets.Label(rect10, "TM_Spells".Translate().CapitalizeFirst());
+ Text.Font = GameFont.Small;
+ Widgets.DrawLineHorizontal(rect.x - 10f, rect10.yMax, rect.width - 15f);
+ Rect rect11 = new Rect(rect.x, rect10.yMax + MagicCardUtility.SectionOffset, rect10.width, MagicCardUtility.PowersColumnHeight);
+ if (comp.customClass != null)
+ {
+ Rect inRect3 = new Rect(rect.x, rect11.y, MagicCardUtility.PowersColumnWidth, MagicCardUtility.PowersColumnHeight);
+ CustomPowersHandler(inRect3, comp, TexButton.TMTex_SkillPointUsed);
+ }
+ }
+ }
+ GUI.EndScrollView();
+ //Widgets.EndScrollView();
+ //GUI.EndGroup();
+ }
+
+ public static void InfoPane(Rect inRect, CompAbilityUserCustom compMagic, Pawn pawn)
+ {
+ Rect rect = new Rect(inRect.x, inRect.y, inRect.width * 0.7f, MagicCardUtility.TextSize);
+ Text.Font = GameFont.Tiny;
+ Widgets.Label(rect, "TM_Level".Translate().CapitalizeFirst() + ": " + compMagic.Data.UserLevel.ToString());
+ Text.Font = GameFont.Tiny;
+ bool godMode = DebugSettings.godMode;
+ if (godMode)
+ {
+ Rect rect2 = new Rect(rect.xMax, inRect.y, inRect.width * 0.3f, MagicCardUtility.TextSize);
+ bool flag = Widgets.ButtonText(rect2, "+", true, false, true);
+ if (flag)
+ {
+ //compMagic.LevelUp(true);
+ compMagic.Data.UserXP += 400; // testing xp bar and levelling
+ }
+ if (false)
+ {
+ Rect rect22 = new Rect(rect.xMax + 60f, inRect.y, 50f, MagicCardUtility.TextSize * 2);
+ bool flag22 = Widgets.ButtonText(rect22, "Reset Skills", true, false, true);
+ if (flag22)
+ {
+ compMagic.ResetSkills();
+ }
+ Rect rect23 = new Rect(rect.xMax + 115f, inRect.y, 50f, MagicCardUtility.TextSize * 2);
+ bool flag23 = Widgets.ButtonText(rect23, "Remove Powers", true, false, true);
+ if (flag23)
+ {
+ compMagic.RemoveAbilityUser();
+ }
+ }
+ }
+ Rect rect4 = new Rect(inRect.x, rect.yMax, inRect.width, MagicCardUtility.TextSize);
+ Text.Font = GameFont.Tiny;
+ Widgets.Label(rect4, "TM_PointsAvail".Translate() + ": " + compMagic.Data.AbilityPoints);
+ Text.Font = GameFont.Tiny;
+ if (!godMode)
+ {
+ //TODO: Display resources info
+ }
+ Rect rect5 = new Rect(rect4.x, rect4.yMax + 3f, inRect.width + 100f, MagicCardUtility.HeaderSize * 0.6f);
+ DrawLevelBar(rect5, compMagic, pawn, inRect);
+ }
+
+ public static void DrawLevelBar(Rect rect, CompAbilityUserCustom compMagic, Pawn pawn, Rect rectG)
+ {
+ bool flag = rect.height > 70f;
+ if (flag)
+ {
+ float num = (rect.height - 70f) / 2f;
+ rect.height = 70f;
+ rect.y += num;
+ }
+ bool flag2 = Mouse.IsOver(rect);
+ if (flag2)
+ {
+ Widgets.DrawHighlight(rect);
+ }
+ TooltipHandler.TipRegion(rect, new TipSignal(() => MagicXPTipString(compMagic.Data), rect.GetHashCode()));
+ float num2 = 14f;
+ bool flag3 = rect.height < 50f;
+ if (flag3)
+ {
+ num2 *= Mathf.InverseLerp(0f, 50f, rect.height);
+ }
+ Text.Anchor = TextAnchor.UpperLeft;
+ Rect rect2 = new Rect(rect.x, rect.y + rect.height / 2f, rect.width, rect.height);
+ rect2 = new Rect(rect2.x, rect2.y, rect2.width, rect2.height - num2);
+ float xpPercent = (compMagic.Data.UserXP - compMagic.Data.XPForLevel(compMagic.Data.UserLevel))
+ / (float)(compMagic.Data.XPForLevel(compMagic.Data.UserLevel + 1) - compMagic.Data.XPForLevel(compMagic.Data.UserLevel));
+ Widgets.FillableBar(rect2, xpPercent, manaTex, BaseContent.GreyTex, false);
+ Rect rect3 = new Rect(rect2.x + 272f + MagicCardUtility.MagicButtonPointSize, rectG.y, 136f, MagicCardUtility.TextSize);
+ Rect rect31 = new Rect(rect2.x + 272f, rectG.y, MagicCardUtility.MagicButtonPointSize, MagicCardUtility.TextSize);
+ Rect rect4 = new Rect(rect3.x + rect3.width + (MagicCardUtility.MagicButtonPointSize * 2), rectG.y, 136f, MagicCardUtility.TextSize);
+ Rect rect41 = new Rect(rect3.x + rect3.width + MagicCardUtility.MagicButtonPointSize, rectG.y, MagicCardUtility.MagicButtonPointSize, MagicCardUtility.TextSize);
+ Rect rect5 = new Rect(rect2.x + 272f + MagicCardUtility.MagicButtonPointSize, rectG.yMin + 24f, 136f, MagicCardUtility.TextSize);
+ Rect rect51 = new Rect(rect2.x + 272f, rectG.yMin + 24f, MagicCardUtility.MagicButtonPointSize, MagicCardUtility.TextSize);
+
+ //TODO: draw global skills? One could also add a fake power and put the global abilities there.
+ }
+
+ public static string MagicXPTipString(CustomData compMagic)
+ {
+ return string.Concat(
+ compMagic.UserXP.ToString(),
+ " / ",
+ compMagic.XPForLevel(compMagic.UserLevel + 1).ToString(),
+ "\n",
+ "TM_MagicXPDesc".Translate()
+ );
+ }
+
+ public static void CustomPowersHandler(Rect inRect, CompAbilityUserCustom compMagic, Texture2D pointTexture)
+ {
+ float yOffset = inRect.y;
+ int itnum = 1;
+ using (List.Enumerator enumerator = compMagic.Data.Powers.GetEnumerator())
+ {
+ while (enumerator.MoveNext())
+ {
+ BasePower power = enumerator.Current;
+ TMAbilityDef ability = (TMAbilityDef)power.AbilityDef;
+ if (ability == TorannMagicDefOf.TM_SoulBond || ability == TorannMagicDefOf.TM_ShadowBolt || ability == TorannMagicDefOf.TM_Dominate)
+ {
+ continue;
+ }
+
+ Text.Font = GameFont.Small;
+ Rect abilityButton = new Rect(MagicCardUtility.MagicCardSize.x / 2f - MagicCardUtility.MagicButtonSize, yOffset, MagicCardUtility.MagicButtonSize, MagicCardUtility.MagicButtonSize);
+ if (itnum > 1)
+ {
+ Widgets.DrawLineHorizontal(0f + 20f, yOffset - 2f, MagicCardUtility.MagicCardSize.x - 40f);
+ }
+ if (power.level < power.maxLevel)
+ {
+
+ TooltipHandler.TipRegion(abilityButton, () => string.Concat(new string[]
+ {
+ power.AbilityDef.label,
+ "\n\nCurrent Level:\n",
+ power.AbilityDef.description,
+ "\n\nNext Level:\n",
+ power.NextLevelAbilityDef.description,
+ "\n\n",
+ "TM_CheckPointsForMoreInfo".Translate()
+ }), 398462);
+
+ }
+ else
+ {
+ TooltipHandler.TipRegion(abilityButton, () => string.Concat(new string[]
+ {
+ power.AbilityDef.label,
+ "\n\n",
+ power.AbilityDef.description,
+ "\n\n",
+ "TM_CheckPointsForMoreInfo".Translate()
+ }), 398462);
+ }
+
+ //float xOffsetSkill = Text.CalcSize("TM_Effeciency".Translate()).x;
+ //float x3 = Text.CalcSize("TM_Versatility".Translate()).x;
+ Rect rect3 = new Rect(0f + MagicCardUtility.SpacingOffset, yOffset + 2f, MagicCardUtility.MagicCardSize.x, MagicCardUtility.ButtonSize * 1.15f);
+
+ //Rect skillRect = new Rect(rect3.x + rect3.width / 2f - xOffsetSkill, rect3.y, (rect3.width - 20f) / 3f, rect3.height);
+ //Rect rect6 = new Rect(rect3.width - x3 * 2f, rect3.y, rect3.width / 3f, rect3.height);
+
+ //bool flag9 = power.abilityDef.label == "Ray of Hope" || power.abilityDef.label == "Soothing Breeze" || power.abilityDef.label == "Frost Ray" || power.abilityDef.label == "AMP" || power.abilityDef.label == "Shadow" || power.abilityDef.label == "Magic Missile" || power.abilityDef.label == "Blink" || power.abilityDef.label == "Summon" || power.abilityDef.label == "Shield"; //add all other buffs or xml based upgrades
+
+ Rect rectLabel = new Rect(0f + 20f, abilityButton.yMin, 350f - 44f, MagicCardUtility.MagicButtonPointSize);
+ //GUI.color = Color.yellow;
+ Widgets.Label(rectLabel, power.AbilityDef.LabelCap);
+ //GUI.color = Color.white;
+ if (power.learned != true)
+ {
+ Widgets.DrawTextureFitted(abilityButton, power.Icon, 1f);
+ Text.Font = GameFont.Tiny;
+ Rect rectLearn = new Rect(abilityButton.xMin - 44f, abilityButton.yMin, 40f, MagicCardUtility.MagicButtonPointSize);
+ if (compMagic.Data.AbilityPoints >= power.learnCost)//&& !power.requiresScroll) // FIXME: the ability can be leveled for 1 point, why?
+ {
+ bool flagLearn = Widgets.ButtonText(rectLearn, "TM_Learn".Translate(), true, false, true) && compMagic.AbilityUser.Faction == Faction.OfPlayer;
+ if (flagLearn)
+ {
+ // power.levelUp(comp); TODO: delegate levelup action to the individual power
+ power.learned = true;
+ if (!(power.AbilityDef?.defName == "TM_TechnoBit"))
+ {
+ compMagic.AddPawnAbility(enumerator.Current.AbilityDef);
+ }
+ if (power.AbilityDef?.defName == "TM_TechnoWeapon")
+ {
+ compMagic.AddPawnAbility(TorannMagicDefOf.TM_NanoStimulant);
+ }
+ compMagic.Data.AbilityPoints -= power.learnCost;
+ }
+ }
+ //else if (power.requiresScroll)
+ //{
+ // Rect rectToLearn = new Rect(rect.xMin - 268f, rect.yMin + 22f, 250f, MagicCardUtility.MagicButtonPointSize);
+ // bool flagLearn = Widgets.ButtonText(rectToLearn, "TM_SpellLocked".Translate(power.abilityDef.LabelCap), false, false, false) && compMagic.AbilityUser.Faction == Faction.OfPlayer;
+ //}
+ else
+ {
+ string message = "" + enumerator.Current.learnCost + " points to " + "TM_Learn".Translate();
+ float size = Text.CalcSize(message).x;
+ Rect rectToLearn = new Rect(abilityButton.xMin - size, abilityButton.yMin, size, MagicCardUtility.MagicButtonPointSize);
+ Widgets.Label(rectToLearn, message);
+ }
+ }
+ else
+ {
+ string level = " " + power.level + " / " + power.maxLevel;
+ Vector2 levelSpace = Text.CalcSize(level);
+ Rect rightOfImage = new Rect(abilityButton.xMax, abilityButton.yMin, levelSpace.x, levelSpace.y); // MagicCardUtility.TextSize);
+ Widgets.DrawTextureFitted(abilityButton, power.Icon, 1f);
+ if (power.maxLevel > 0)
+ {
+ Widgets.Label(rightOfImage, level);
+ }
+ if (enumerator.Current.level >= power.maxLevel
+ || compMagic.Data.AbilityPoints < power.upgradeCost
+ || compMagic.AbilityUser.Faction != Faction.OfPlayer)
+ {
+ }
+ else
+ {
+ Rect belowLevel = new Rect(abilityButton.xMax, abilityButton.yMax - levelSpace.y, levelSpace.x, levelSpace.y); // MagicCardUtility.TextSize);
+ bool flag1 = Widgets.ButtonText(belowLevel, "+", true, false, true);
+ if (flag1)
+ {
+ // power.levelUp(comp); TODO: delegate levelup action to the individual power
+ compMagic.LevelUpPower(power);
+ compMagic.Data.AbilityPoints -= power.upgradeCost;
+ }
+ }
+ }
+
+ Text.Font = GameFont.Tiny;
+ float xOffset = rect3.x;
+
+ float xSpacing = (MagicCardUtility.MagicCardSize.x / power.Upgrades.Count) - MagicCardUtility.SpacingOffset;
+ for (int i = 0; i < power.Upgrades.Count; ++i)
+ {
+ if (power.Upgrades[i] == null) { Log.Message("Null upgrade key in power " + ((power as CustomPower)?.localDef.defName ?? power.AbilityDef.defName)); continue; }
+ MagicPowerSkill skill = compMagic.Data.Upgrades.TryGetValue(power.Upgrades[i]);
+ CustomSkillHandler(xOffset, compMagic, power, skill, rect3);
+ itnum++;
+ xOffset += xSpacing;
+ }
+ yOffset += MagicCardUtility.MagicButtonSize + MagicCardUtility.TextSize + 4f;//MagicCardUtility.SpacingOffset; //was 4f
+ }
+ }
+ }
+
+ public static void CustomSkillHandler(float xOffset, CompAbilityUserCustom compMagic, BasePower power, MagicPowerSkill skill, Rect rect3)
+ {
+ Rect rect4 = new Rect(xOffset + MagicCardUtility.MagicButtonPointSize, rect3.yMax, MagicCardUtility.MagicCardSize.x / 3f, rect3.height);
+ Rect rect41 = new Rect(xOffset, rect4.y, MagicCardUtility.MagicButtonPointSize, MagicCardUtility.MagicButtonPointSize);
+ Rect rect42 = new Rect(rect41.x, rect4.y, rect4.width - MagicCardUtility.MagicButtonPointSize, rect4.height / 2);
+ //string description = skill.shouldTranslate() ? skill.desc.Translate().ToString() : skill.desc;
+ //string description = skill.getLocalizedDescription();
+ //TODO: find a way to add translation without modifying the language files directly?
+ string description = false ? skill.desc.Translate().ToString() : skill.desc;
+ string name = false ? skill.label.Translate().ToString() : skill.label;
+ TooltipHandler.TipRegion(rect42, new TipSignal(() => description, rect4.GetHashCode()));
+ Widgets.Label(rect4, name + ": " + skill.level + " / " + skill.levelMax);
+ if (skill.level < skill.levelMax
+ && compMagic.Data.AbilityPoints >= skill.costToLevel
+ && power.learned)
+ {
+ bool flag12 = Widgets.ButtonText(rect41, "+", true, false, true) && compMagic.AbilityUser.Faction == Faction.OfPlayer;
+ if (flag12)
+ {
+ bool flag17 = compMagic.AbilityUser.story != null && compMagic.AbilityUser.story.DisabledWorkTagsBackstoryAndTraits == WorkTags.Violent && power.AbilityDef.MainVerb.isViolent;
+ if (flag17)
+ {
+ Messages.Message("IsIncapableOfViolenceLower".Translate(
+ compMagic.Pawn.Name.ToString()
+ ), MessageTypeDefOf.RejectInput);
+ }
+ else
+ {
+ skill.level++;
+ compMagic.Data.AbilityPoints -= skill.costToLevel;
+ }
+ }
+ }
+ }
+
+ }
+}
diff --git a/Source/TMagic/TMagic/MagicAbility.cs b/Source/TMagic/TMagic/MagicAbility.cs
index 87496a3c..00c6cf2f 100644
--- a/Source/TMagic/TMagic/MagicAbility.cs
+++ b/Source/TMagic/TMagic/MagicAbility.cs
@@ -58,6 +58,10 @@ public MagicAbility()
{
}
+ public MagicAbility(AbilityData data) : base(data)
+ {
+ }
+
public MagicAbility(CompAbilityUser abilityUser) : base(abilityUser)
{
this.abilityUser = (abilityUser as CompAbilityUserMagic);
@@ -96,13 +100,19 @@ public override void PostAbilityAttempt() //commented out in CompAbilityUserMag
this.MagicUser.Mana.UseMagicPower(this.MagicUser.ActualManaCost(magicDef)/2f);
}
else
- {
+ {
this.MagicUser.Mana.UseMagicPower(this.MagicUser.ActualManaCost(magicDef));
}
if(this.magicDef != TorannMagicDefOf.TM_TransferMana && magicDef.abilityHediff == null)
- {
- this.MagicUser.MagicUserXP += (int)((magicDef.manaCost * 300) * this.MagicUser.xpGain * settingsRef.xpMultiplier);
+ {
+ int xp = (int)((magicDef.manaCost * 300) * this.MagicUser.xpGain * settingsRef.xpMultiplier);
+ this.MagicUser.MagicUserXP += xp;
+ CompAbilityUserCustom custom = this.AbilityUser.Pawn.TryGetComp();
+ if (custom != null && custom.Data.ReturnMatchingPower(this.Def, true)?.learned == true)
+ {
+ custom.Data.UserXP += xp;
+ }
}
}
else if (this.MagicUser.Pawn.story.traits.HasTrait(TorannMagicDefOf.Faceless))
diff --git a/Source/TMagic/TMagic/MightAbility.cs b/Source/TMagic/TMagic/MightAbility.cs
index 57c745bf..70ef444c 100644
--- a/Source/TMagic/TMagic/MightAbility.cs
+++ b/Source/TMagic/TMagic/MightAbility.cs
@@ -97,6 +97,7 @@ public override void PostAbilityAttempt()
bool flag = this.mightDef != null;
if (flag)
{
+ int expGain = 0;
if (mightDef.consumeEnergy)
{
bool flag3 = this.MightUser.Stamina != null;
@@ -119,6 +120,19 @@ public override void PostAbilityAttempt()
HealthUtility.AdjustSeverity(this.Pawn, TorannMagicDefOf.TM_ChiHD, -100 * this.ActualChiCost);
this.MightUser.MightUserXP += (int)((mightDef.chiCost * 100) * this.MightUser.xpGain * settingsRef.xpMultiplier);
}
+ expGain += (int)((mightDef.staminaCost * 180) * this.MightUser.xpGain * settingsRef.xpMultiplier);
+
+ }
+ if (this.mightDef.chiCost != 0)
+ {
+ HealthUtility.AdjustSeverity(this.Pawn, TorannMagicDefOf.TM_ChiHD, -100 * this.ActualChiCost);
+ expGain += (int)((mightDef.chiCost * 100) * this.MightUser.xpGain * settingsRef.xpMultiplier);
+ }
+ this.MightUser.MightUserXP += expGain;
+ CompAbilityUserCustom custom = this.AbilityUser.Pawn.TryGetComp();
+ if (custom != null && custom.Data.ReturnMatchingPower(this.Def, true)?.learned == true)
+ {
+ custom.Data.UserXP += expGain;
}
}
}
diff --git a/Source/TMagic/TMagic/TMAbilityCost.cs b/Source/TMagic/TMagic/TMAbilityCost.cs
new file mode 100644
index 00000000..5e0c8bca
--- /dev/null
+++ b/Source/TMagic/TMagic/TMAbilityCost.cs
@@ -0,0 +1,42 @@
+using System.Text;
+using AbilityUser;
+using RimWorld;
+using Verse;
+using System.Collections.Generic;
+
+namespace TorannMagic
+{
+ public abstract class TMAbilityCostProperties
+ {
+ public abstract TMAbilityCost ForPawn(Pawn pawn);
+ }
+ public abstract class TMAbilityCost
+ {
+ public abstract float BaseCost { get; }
+ public abstract string Description { get; }
+ public abstract bool CanPayCost(out float actualCost);
+ public abstract bool PayCost(out float actualPaid);
+ public abstract bool RefundCost(out float actualRefund);
+ }
+ public class TMAbilityBadCost : TMAbilityCost
+ {
+ public override float BaseCost => -1;
+ public override string Description
+ {
+ get => "impossible cost";
+ }
+ public override bool CanPayCost(out float actualCost)
+ {
+ actualCost = BaseCost;
+ return false;
+ }
+ public override bool PayCost(out float actualPaid)
+ {
+ return CanPayCost(out actualPaid);
+ }
+ public override bool RefundCost(out float actualRefund)
+ {
+ return CanPayCost(out actualRefund);
+ }
+ }
+}
diff --git a/Source/TMagic/TMagic/TMAbilityCost_Hediff.cs b/Source/TMagic/TMagic/TMAbilityCost_Hediff.cs
new file mode 100644
index 00000000..4091ae30
--- /dev/null
+++ b/Source/TMagic/TMagic/TMAbilityCost_Hediff.cs
@@ -0,0 +1,86 @@
+using System.Text;
+using AbilityUser;
+using RimWorld;
+using Verse;
+using System.Collections.Generic;
+
+namespace TorannMagic
+{
+ public class TMAbilityHediffCostProp : TMAbilityCostProperties
+ {
+ private HediffDef hediffDef;
+ private float baseCost = 0f;
+ private float baseCostReductionPerUpgrade = 0f;
+ public override TMAbilityCost ForPawn(Pawn pawn)
+ {
+ TMAbilityHediffCost.Backup backup = () => pawn.health.hediffSet.GetFirstHediffOfDef(hediffDef);
+ return new TMAbilityHediffCost(hediffDef, baseCost, backup);
+ }
+ }
+
+ public class TMAbilityHediffCost : TMAbilityCost
+ {
+ public delegate Hediff Backup();
+
+ private string hediffLabel;
+ private Hediff found;
+ private Backup backup;
+ private float baseCost = 0f;
+ //private float baseCostReductionPerUpgrade = 0f;
+ private Hediff hediff
+ {
+ get {
+ if (found == null) // TODO: limit check frequency
+ {
+ found = backup();
+ }
+ return found;
+ }
+ }
+
+ public TMAbilityHediffCost(HediffDef def, float baseCost, Backup backup)
+ {
+ this.hediffLabel = def.LabelCap;
+ this.baseCost = baseCost;
+ this.backup = backup;
+ }
+
+ public override float BaseCost
+ {
+ get => baseCost;
+ }
+ public override string Description
+ {
+ get => string.Concat(BaseCost, " ", hediffLabel);
+ }
+ public override bool CanPayCost(out float actualCost)
+ {
+ actualCost = baseCost;
+ return hediff?.Severity > actualCost;
+ }
+ public override bool PayCost(out float actualCost)
+ {
+ if (hediff?.Severity >= baseCost)
+ {
+ actualCost = baseCost;
+ hediff.Severity -= baseCost;
+ return true;
+ }
+ else
+ {
+ actualCost = baseCost;
+ return false;
+ }
+ }
+ public override bool RefundCost(out float actualRefund)
+ {
+ actualRefund = baseCost;
+ if (hediff != null)
+ {
+ hediff.Severity += baseCost;
+ return true;
+ }
+ return false;
+ }
+ }
+}
diff --git a/Source/TMagic/TMagic/TMAbilityCost_Need.cs b/Source/TMagic/TMagic/TMAbilityCost_Need.cs
new file mode 100644
index 00000000..5cb3a9a4
--- /dev/null
+++ b/Source/TMagic/TMagic/TMAbilityCost_Need.cs
@@ -0,0 +1,71 @@
+using System.Text;
+using AbilityUser;
+using RimWorld;
+using Verse;
+using System.Collections.Generic;
+
+namespace TorannMagic
+{
+ public class TMAbilityNeedCostProp : TMAbilityCostProperties
+ {
+ private NeedDef needDef;
+ private float baseCost = 0f;
+ private float baseCostReductionPerUpgrade = 0f;
+ public override TMAbilityCost ForPawn(Pawn pawn)
+ {
+ Need need = pawn.needs.TryGetNeed(needDef);
+ return need != null ? new TMAbilityNeedCost(need, baseCost) : null;
+ }
+ }
+
+ public class TMAbilityNeedCost : TMAbilityCost
+ {
+ private Need need;
+ private float baseCost = 0f;
+ //private float baseCostReductionPerUpgrade = 0f;
+
+ public TMAbilityNeedCost(Need need, float baseCost)
+ {
+ this.need = need;
+ this.baseCost = baseCost / 100f;
+ Log.Message("Init cost " + baseCost + need.LabelCap);
+ }
+
+ public override float BaseCost
+ {
+ get => baseCost;
+ }
+ public override string Description
+ {
+ get => string.Concat(BaseCost * 100f, " ", need.LabelCap);
+ }
+ public override bool CanPayCost(out float actualCost)
+ {
+ actualCost = baseCost;
+ return need.CurLevel > actualCost;
+ }
+ public override bool PayCost(out float actualCost)
+ {
+ if (need?.CurLevel < baseCost)
+ {
+ actualCost = baseCost;
+ return false;
+ }
+ else
+ {
+ actualCost = baseCost;
+ need.CurLevel -= baseCost;
+ return true;
+ }
+ }
+ public override bool RefundCost(out float actualRefund)
+ {
+ actualRefund = baseCost;
+ if (need != null)
+ {
+ need.CurLevel += baseCost;
+ }
+ return true;
+ }
+ }
+}
diff --git a/Source/TMagic/TMagic/TMAbilityDef.cs b/Source/TMagic/TMagic/TMAbilityDef.cs
index 780bc1a2..8b2668b9 100644
--- a/Source/TMagic/TMagic/TMAbilityDef.cs
+++ b/Source/TMagic/TMagic/TMAbilityDef.cs
@@ -13,6 +13,7 @@ public class TMAbilityDef : AbilityUser.AbilityDef
public float staminaCost = 0f;
public float bloodCost = 0f;
public float chiCost = 0f;
+ public TMAbilityCostProperties customCost = null;
public bool consumeEnergy = true;
public int abilityPoints = 1;
public float learnChance = 1f;
diff --git a/Source/TMagic/TMagic/TMDefs/TM_CustomClass.cs b/Source/TMagic/TMagic/TMDefs/TM_CustomClass.cs
index 84225057..1b9b4a49 100644
--- a/Source/TMagic/TMagic/TMDefs/TM_CustomClass.cs
+++ b/Source/TMagic/TMagic/TMDefs/TM_CustomClass.cs
@@ -26,6 +26,7 @@ public class TM_CustomClass
public List classFighterAbilities = new List();
public List learnableSpells = new List();
public List learnableSkills = new List();
+ public List customPowers = new List();
//Class Designations
public bool isMage = false;
diff --git a/Source/TMagic/TMagic/TorannMagic.csproj b/Source/TMagic/TMagic/TorannMagic.csproj
index 3857f25c..9688cdc1 100644
--- a/Source/TMagic/TMagic/TorannMagic.csproj
+++ b/Source/TMagic/TMagic/TorannMagic.csproj
@@ -641,6 +641,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Source/TMagic/TMagic/Verb_ApplyRandomHediffFromList.cs b/Source/TMagic/TMagic/Verb_ApplyRandomHediffFromList.cs
new file mode 100644
index 00000000..8cdc2266
--- /dev/null
+++ b/Source/TMagic/TMagic/Verb_ApplyRandomHediffFromList.cs
@@ -0,0 +1,88 @@
+using Verse;
+using AbilityUser;
+using System.Collections.Generic;
+
+namespace TorannMagic
+{
+ public class Verb_ApplyRandomHediffFromList_Properties : VerbProperties_Ability
+ {
+ public List hediffDefs;
+ public int baseCount = 1;
+ public UpgradeDef countUpgrade;
+ public float baseSeverity = 0.5f;
+ public UpgradeDef severityUpgrade;
+ public float severityIncreasePerUpgrade = 0.1f;
+ public bool useEachOnceMax = false;
+
+ public Verb_ApplyRandomHediffFromList_Properties() : base()
+ {
+ this.verbClass = verbClass ?? typeof(Verb_ApplyRandomHediffFromList);
+ }
+ }
+
+ public class Verb_ApplyRandomHediffFromList : Verb_SB
+ {
+ Pawn target => currentTarget.Pawn;
+ Verb_ApplyRandomHediffFromList_Properties Properties => this.verbProps as Verb_ApplyRandomHediffFromList_Properties;
+ List Effects => Properties?.hediffDefs;
+ int CountUpgrade => Properties.countUpgrade == null ? 0
+ : CasterPawn?.TryGetComp().Data.GetUpgradeLevel(Properties.countUpgrade.UpgradeId) ?? 0;
+ int SeverityUpgrade => Properties.severityUpgrade == null ? 0
+ : CasterPawn?.TryGetComp().Data.GetUpgradeLevel(Properties.severityUpgrade?.UpgradeId) ?? 0;
+
+ protected override bool TryCastShot()
+ {
+ if (Properties == null)
+ {
+ Log.Warning("The TorannMagic.Verb_ApplyRandomHediffFromList cannot be used outside a TorannMagic.Verb_ApplyRandomHediffFromList_Properties, check abilityDef " + this.Ability.Def.defName);
+ return false;
+ }
+ if (target != null )
+ {
+ int amountToApply = Properties.baseCount + CountUpgrade;
+ float severity = Properties.baseSeverity + Properties.severityIncreasePerUpgrade * SeverityUpgrade;
+
+ if (Properties.useEachOnceMax)
+ {
+ List toApply;
+ if (amountToApply >= Effects.Count)
+ {
+ toApply = Effects;
+ }
+ else
+ {
+ toApply = new List(amountToApply);
+ for (int i = 0; toApply.Count < amountToApply; ++i)
+ {
+ float rand = Rand.Value;
+ Log.Message("rand: " + rand);
+ Log.Message("target: " + (amountToApply - toApply.Count) / (float)(Effects.Count - i));
+ if (rand < (amountToApply - toApply.Count) / (float)(Effects.Count - i))
+ {
+ toApply.Add(Effects[i]);
+ }
+ }
+ }
+ foreach (HediffDef h in toApply)
+ {
+ HealthUtility.AdjustSeverity(target, h, severity);
+ }
+ }
+ else
+ {
+ Dictionary toApply = new Dictionary(Properties.hediffDefs.Count);
+ for (int i = 0; i < amountToApply; ++i)
+ {
+ HediffDef def = Effects.RandomElement();
+ toApply.SetOrAdd(def, toApply.TryGetValue(def) + severity);
+ }
+ foreach (HediffDef d in Effects)
+ {
+ HealthUtility.AdjustSeverity(target, d, toApply.TryGetValue(d));
+ }
+ }
+ }
+ return false;
+ }
+ }
+}
diff --git a/Source/TMagic/TMagic/Verb_MedicalSupply.cs b/Source/TMagic/TMagic/Verb_MedicalSupply.cs
new file mode 100644
index 00000000..64391a1b
--- /dev/null
+++ b/Source/TMagic/TMagic/Verb_MedicalSupply.cs
@@ -0,0 +1,92 @@
+using RimWorld;
+using System;
+using Verse;
+using AbilityUser;
+using UnityEngine;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace TorannMagic
+{
+ public class Verb_MedicalSupply : Verb_UseAbility
+ {
+ bool validTarg;
+ public override bool CanHitTargetFrom(IntVec3 root, LocalTargetInfo targ)
+ {
+ if (targ.IsValid && targ.CenterVector3.InBounds(base.CasterPawn.Map) && !targ.Cell.Fogged(base.CasterPawn.Map))
+ {
+ if ((root - targ.Cell).LengthHorizontal < this.verbProps.range)
+ {
+ validTarg = true;
+ }
+ else
+ {
+ //out of range
+ validTarg = false;
+ }
+ }
+ else
+ {
+ validTarg = false;
+ }
+ return validTarg;
+ }
+
+ protected override bool TryCastShot()
+ {
+ bool result = false;
+ bool success;
+
+ Pawn pawn = this.CasterPawn;
+ Map map = this.CasterPawn.Map;
+ ModOptions.SettingsRef settingsRef = new ModOptions.SettingsRef();
+ //if (pawn != null && !pawn.Downed)
+
+ List validParts = new List();
+ Thing medicalThing = null;
+ if (GetMedicalSupplyFromCell(this.currentTarget.Cell, this.CasterPawn, out medicalThing, true))
+ {
+ float potency = medicalThing.GetStatValue(StatDefOf.MedicalPotency);
+ float supplyGain = 20f * potency * potency;
+ if (!medicalThing.DestroyedOrNull() && medicalThing.stackCount <= 0)
+ {
+ medicalThing.Destroy(DestroyMode.Vanish);
+ }
+ else
+ {
+ medicalThing.SplitOff(1).Destroy(DestroyMode.Vanish);
+ }
+
+ HealthUtility.AdjustSeverity(this.CasterPawn, HediffDef.Named("TM_MedicalSupplyHD"), supplyGain);
+ }
+ else
+ {
+ //todo: notify when invalid target
+ //Messages.Message("TM_NoMedicalSupply".Translate(this.CasterPawn.LabelShort), MessageTypeDefOf.RejectInput, false);
+ Log.Warning("failed to TryCastShot");
+ }
+ this.burstShotsLeft = 0;
+
+ return result;
+ }
+
+ public static bool GetMedicalSupplyFromCell(IntVec3 cell, Pawn pawn, out Thing medicalSupply, bool manualCast = false)
+ {
+ List thingList = cell.GetThingList(pawn.Map);
+ medicalSupply = null;
+ for (int i = 0; i < thingList.Count; i++)
+ {
+ if (thingList[i] != null && !(thingList[i] is Pawn) && !(thingList[i] is Building) && (manualCast || !thingList[i].IsForbidden(pawn)))
+ {
+ if (thingList[i].def.statBases != null && thingList[i].GetStatValue(StatDefOf.MedicalPotency) > 0.25f) // random non-medical itens have a medical potency of 0.2
+ {
+ medicalSupply = thingList[i];
+ Log.Message("medicine item: " + medicalSupply.LabelShort + " with potency " + medicalSupply.GetStatValue(StatDefOf.MedicalPotency));
+ break;
+ }
+ }
+ }
+ return medicalSupply != null;
+ }
+ }
+}
diff --git a/Textures/UI/Emergency.png b/Textures/UI/Emergency.png
new file mode 100644
index 00000000..64c3c2a7
Binary files /dev/null and b/Textures/UI/Emergency.png differ
diff --git a/Textures/UI/Medigel.png b/Textures/UI/Medigel.png
new file mode 100644
index 00000000..79a605a3
Binary files /dev/null and b/Textures/UI/Medigel.png differ