public static int GetToolTransmuteRadius() { if (EquivalentExchange.IsShiftKeyPressed()) { return(0); } return(Util.GetAlchemyPowerLevel(EquivalentExchange.AlchemyLevel) - 1); }
//handles all the things. public override void Entry(IModHelper helper) { //set the static instance variable. is this an oxymoron? instance = this; //preserve this entry method's helper class because it's.. helpful. instance.eeHelper = helper; //read the config file, poached from horse whistles, get the configured keys and settings Config = helper.ReadConfig <ConfigurationModel>(); //add handler for the "transmute/copy" button. ControlEvents.KeyPressed += ControlEvents_KeyPressed; //exclusively to figure out if ctrl or shift have been let go of. ControlEvents.KeyReleased += ControlEvents_KeyReleased; //wire up the library scraping function to occur on save-loading to defer recipe scraping until all mods are loaded, optimistically. SaveEvents.AfterLoad += SaveEvents_AfterLoad; //we need this to save our alchemists['] data SaveEvents.BeforeSave += SaveEvents_BeforeSave; //set texture files in memory, they're tiny things. DrawingUtil.HandleTextureCaching(); //trying something completely different from a patched event hook... //gonna try using this to detect the night event heuristically. GameEvents.UpdateTick += GameEvents_UpdateTick; //wire up the PreRenderHUD event so I can display info bubbles when needed GraphicsEvents.OnPreRenderHudEvent += GraphicsEvents_OnPreRenderHudEvent; //check for experience bars mod: if it's here we draw hud elements for the new alchemy skill CheckForExperienceBarsMod(); if (hasExperienceBarsMod) { GraphicsEvents.OnPostRenderHudEvent += GraphicsEvents_OnPostRenderHudEvent; } //check for all professions mod: if it's here we run a wireup to give the player all skills professions at the right time (or after), when present. CheckForAllProfessionsMod(); if (hasAllProfessionsMod) { LocationEvents.CurrentLocationChanged += LocationEvents_CurrentLocationChanged;; } //add a debug option to give yourself experience Helper.ConsoleCommands.Add("player_givealchemyexp", "player_givealchemyexp <amount>", GiveAlchemyExperience); //post render event for skills menu GraphicsEvents.OnPostRenderGuiEvent += DrawAfterGUI; //check for chase's skills checkForLuck(); checkForCooking(); }
//handles all the things. public override void Entry(IModHelper helper) { //set the static instance variable. is this an oxymoron? instance = this; //read the config file, poached from horse whistles, get the configured keys and settings Config = helper.ReadConfig <ConfigurationModel>(); //add handler for the "transmute/copy" button. ControlEvents.KeyPressed += ControlEvents_KeyPressed; //exclusively to figure out if ctrl or shift have been let go of. ControlEvents.KeyReleased += ControlEvents_KeyReleased; //wire up the library scraping function to occur on save-loading to defer recipe scraping until all mods are loaded, optimistically. SaveEvents.AfterLoad += SaveEvents_AfterLoad; //we need this to save our alchemists['] data SaveEvents.BeforeSave += SaveEvents_BeforeSave; //set texture files in memory, they're tiny things. DrawingUtil.HandleTextureCaching(); //handles high resolution update ticks, like regeneration and held keys. GameEvents.UpdateTick += GameEvents_UpdateTick; //wire up the PreRenderHUD event so I can display info bubbles when needed GraphicsEvents.OnPreRenderHudEvent += GraphicsEvents_OnPreRenderHudEvent; // handles end of night event requirements like alchemy energy being restored and level ups. SpaceEvents.ShowNightEndMenus += SpaceEvents_ShowNightEndMenus; // stuff we have to do for multiplayer now, handles client join events to cascade data to the non-hosts. SpaceEvents.ServerGotClient += SpaceEvents_ServerGotClient; // handle looking out for the slime gift to the wizard that gates the alchemy content SpaceEvents.AfterGiftGiven += SpaceEvents_AfterGiftGiven; Networking.RegisterMessageHandler(MSG_DATA, OnDataMessage); Networking.RegisterMessageHandler(MSG_EXPERIENCE, OnExpMessage); Networking.RegisterMessageHandler(MSG_LEVEL, OnLevelMessage); Networking.RegisterMessageHandler(MSG_CURRENT_ENERGY, OnCurrentEnergyMessage); Networking.RegisterMessageHandler(MSG_MAX_ENERGY, OnMaxEnergyMessage); Networking.RegisterMessageHandler(MSG_TOTAL_VALUE_TRANSMUTED, OnTransmutedValueMessage); Networking.RegisterMessageHandler(MSG_REGEN_TICK, OnRegenTick); Networking.RegisterMessageHandler(MSG_IS_SLIME_GIVEN_TO_WIZARD, OnSlimeGivenToWizardMessage); Skills.RegisterSkill(skill = new AlchemySkill()); }
public static bool HandleTransmuteEvent(Item heldItem, int actualValue) { // if the recipes list doesn't contain the item you're holding, you can't transmute that. var recipes = EquivalentExchange.GetTransmutationFormulas(); // sorted recipe list var validRecipes = recipes.GetRecipesForOutput(heldItem.parentSheetIndex); if (validRecipes.Count == 0) { return(true); } // use more sorting magic to find a potential recipe from the player's inventory // prioritize the recipes by their input costs (cheapest inputs are prioritized by the function GetRecipesForOutput) var optimalRecipe = validRecipes.FindBestRecipe(Game1.player); // something has stopped us from finding a valid recipe. The player either doesn't have the necessary items // or doesn't have the energy to do the transmutation. if (optimalRecipe == null) { return(true); } var breakRepeaterLoop = false; Alchemy.HandleAlchemyEnergyDeduction(optimalRecipe.GetEnergyCost(), false); if (optimalRecipe.GetEnergyCost() > EquivalentExchange.CurrentEnergy) { breakRepeaterLoop = true; } Alchemy.IncreaseTotalTransmuteValue((int)Math.Floor(Math.Max(1D, optimalRecipe.GetEnergyCost()))); Util.TakeItemFromPlayer(optimalRecipe.InputId, optimalRecipe.GetInputCost(), Game1.player); Item spawnedItem = heldItem.getOne(); spawnedItem.Stack = optimalRecipe.GetOutputQuantity(); Util.GiveItemToPlayer((StardewValley.Object)spawnedItem, Game1.player); SoundUtil.PlayMagickySound(); return(breakRepeaterLoop); }
//handles draining energy/stamina on successful transmute public static void HandleAlchemyEnergyDeduction(double energyCost, bool isForcedStaminaDrain) { double remainingStaminaCost = energyCost; // if the stamina drain is "forced" it means you can't pay for this transaction with energy // the *entire* cost goes to stamina. if (!isForcedStaminaDrain) { //if you have any alkahestry energy, it will try to use as much as it can double alkahestryCost = (double)Math.Min(EquivalentExchange.CurrentEnergy, energyCost); //and deduct that from whatever stamina cost might be left over (which may be all of it) remainingStaminaCost -= alkahestryCost; Alchemy.ReduceAlkahestryEnergy(alkahestryCost); } Game1.player.Stamina -= (float)remainingStaminaCost; EquivalentExchange.AddAlchemyExperience((int)Math.Floor(Math.Max(energyCost, 1D))); }
public static void CleanDeprecatedProfessions() { foreach (var deprecatedProfession in DeprecatedProfessionIntegers) { if (EquivalentExchange.HasProfession(deprecatedProfession)) { Game1.player.professions.Remove(deprecatedProfession); switch (deprecatedProfession) { case OldShaperId: Game1.player.professions.Add(AlchemySkill.ProfessionShaper.GetVanillaId()); break; case OldSageId: Game1.player.professions.Add(AlchemySkill.ProfessionSage.GetVanillaId()); break; case OldTransmuterId: Game1.player.professions.Add(AlchemySkill.ProfessionTransmuter.GetVanillaId()); break; case OldAdeptId: Game1.player.professions.Add(AlchemySkill.ProfessionAdept.GetVanillaId()); break; case OldAurumancerId: Game1.player.professions.Add(AlchemySkill.ProfessionAurumancer.GetVanillaId()); break; case OldConduitId: Game1.player.professions.Add(AlchemySkill.ProfessionConduit.GetVanillaId()); break; } } } }
private static bool DoToolFunction(GameLocation location, StardewValley.Farmer who, Tool tool, int x, int y) { bool performedAction = false; Vector2 index = new Vector2(x, y); Vector2 vector2 = new Vector2((float)(x + 0.5), (float)(y + 0.5)); if (tool is MeleeWeapon && tool.Name.ToLower().Contains("scythe")) { var snapshotPlayerExperience = Game1.player.experiencePoints; if (location.objects[index] != null) { StardewValley.Object hitObject = location.objects[index]; if (hitObject.name.Contains("Weed") && hitObject.performToolAction(tool, location)) { if (hitObject.type == "Crafting" && hitObject.fragility != 2) { location.debris.Add(new Debris(hitObject.bigCraftable ? -hitObject.parentSheetIndex : hitObject.parentSheetIndex, index, index)); } hitObject.performRemoveAction(index, location); location.objects.Remove(index); performedAction = true; } } else if (location.terrainFeatures.ContainsKey(index) && location.terrainFeatures[index].performToolAction(tool, 0, index, (GameLocation)null)) { location.terrainFeatures.Remove(index); performedAction = true; } RestorePlayerExperience(snapshotPlayerExperience); } else if (tool is Axe) { var snapshotPlayerExperience = Game1.player.experiencePoints; Rectangle rectangle = new Rectangle(x * Game1.tileSize, y * Game1.tileSize, Game1.tileSize, Game1.tileSize); location.performToolAction(tool, x, y); if (location.terrainFeatures.ContainsKey(index) && location.terrainFeatures[index].performToolAction(tool, 0, index, (GameLocation)null)) { location.terrainFeatures.Remove(index); performedAction = true; } Rectangle boundingBox; if (location.largeTerrainFeatures != null) { for (int index2 = location.largeTerrainFeatures.Count - 1; index2 >= 0; --index2) { boundingBox = location.largeTerrainFeatures[index2].getBoundingBox(); if (boundingBox.Intersects(rectangle) && location.largeTerrainFeatures[index2].performToolAction(tool, 0, index, (GameLocation)null)) { location.largeTerrainFeatures.RemoveAt(index2); } } } if (location.terrainFeatures.ContainsKey(index) && location.terrainFeatures[index] is Tree) { if (!(location.terrainFeatures[index] as Tree).stump || EquivalentExchange.IsShiftKeyPressed()) { performedAction = true; } } if (!location.Objects.ContainsKey(index) || location.Objects[index].Type == null || !location.Objects[index].performToolAction(tool, location)) { return(performedAction); } if (location.Objects[index].type.Equals("Crafting") && location.Objects[index].fragility != 2) { var debris1 = location.debris; int objectIndex = location.Objects[index].bigCraftable ? -location.Objects[index].ParentSheetIndex : location.Objects[index].ParentSheetIndex; Debris debris2 = new Debris(objectIndex, index, index); debris1.Add(debris2); } location.Objects[index].performRemoveAction(index, location); location.Objects.Remove(index); performedAction = true; RestorePlayerExperience(snapshotPlayerExperience); } else if (tool is Pickaxe) { var snapshotPlayerExperience = Game1.player.experiencePoints; int power = who.toolPower; if (location.performToolAction(tool, x, y)) { return(true); } StardewValley.Object objectHit = (StardewValley.Object)null; location.Objects.TryGetValue(index, out objectHit); if (objectHit == null) { if (location.terrainFeatures.ContainsKey(index) && location.terrainFeatures[index].performToolAction(tool, 0, index, (GameLocation)null)) { location.terrainFeatures.Remove(index); performedAction = true; } } if (objectHit != null) { if (objectHit.Name.Equals("Stone")) { Game1.playSound("hammer"); if (objectHit.minutesUntilReady > 0) { int num3 = Math.Max(1, tool.upgradeLevel + 1); objectHit.minutesUntilReady.Set(objectHit.minutesUntilReady - num3); objectHit.shakeTimer = 200; if (objectHit.minutesUntilReady > 0) { Game1.createRadialDebris(Game1.currentLocation, 14, x, y, Game1.random.Next(2, 5), false, -1, false, -1); return(performedAction); } } if (objectHit.ParentSheetIndex < 200 && !Game1.objectInformation.ContainsKey(objectHit.ParentSheetIndex + 1)) { location.TemporarySprites.Add(new TemporaryAnimatedSprite(objectHit.ParentSheetIndex + 1, 300f, 1, 2, new Vector2((float)(x) * Game1.tileSize, (float)(y) * Game1.tileSize), true, objectHit.flipped) { alphaFade = 0.01f }); } else { location.TemporarySprites.Add(new TemporaryAnimatedSprite(47, new Vector2((float)(x * Game1.tileSize), (float)(y * Game1.tileSize)), Color.Gray, 10, false, 80f, 0, -1, -1f, -1, 0)); } Game1.createRadialDebris(location, 14, x, y, Game1.random.Next(2, 5), false, -1, false, -1); location.TemporarySprites.Add(new TemporaryAnimatedSprite(46, new Vector2((float)(x * Game1.tileSize), (float)(y * Game1.tileSize)), Color.White, 10, false, 80f, 0, -1, -1f, -1, 0) { motion = new Vector2(0.0f, -0.6f), acceleration = new Vector2(0.0f, 1f / 500f), alphaFade = 0.015f }); if (!location.Name.StartsWith("UndergroundMine")) { if (objectHit.parentSheetIndex == 343 || objectHit.parentSheetIndex == 450) { Random random = new Random((int)Game1.stats.DaysPlayed + (int)Game1.uniqueIDForThisGame / 2 + x * 2000 + y); if (random.NextDouble() < 0.035 && Game1.stats.DaysPlayed > 1U) { Game1.createObjectDebris(535 + (Game1.stats.DaysPlayed <= 60U || random.NextDouble() >= 0.2 ? (Game1.stats.DaysPlayed <= 120U || random.NextDouble() >= 0.2 ? 0 : 2) : 1), x, y, tool.getLastFarmerToUse().uniqueMultiplayerID); } if (random.NextDouble() < 0.035 * (who.professions.Contains(21) ? 2.0 : 1.0) && Game1.stats.DaysPlayed > 1U) { Game1.createObjectDebris(382, x, y, tool.getLastFarmerToUse().uniqueMultiplayerID); } if (random.NextDouble() < 0.01 && Game1.stats.DaysPlayed > 1U) { Game1.createObjectDebris(390, x, y, tool.getLastFarmerToUse().uniqueMultiplayerID); } } location.breakStone(objectHit.parentSheetIndex, x, y, who, new Random((int)Game1.stats.DaysPlayed + (int)Game1.uniqueIDForThisGame / 2 + x * 4000 + y)); } else { Game1.mine.checkStoneForItems(objectHit.ParentSheetIndex, x, y, who); } if (objectHit.minutesUntilReady > 0) { return(performedAction); } location.Objects.Remove(index); Game1.playSound("stoneCrack"); performedAction = true; } else { if (!objectHit.performToolAction(tool, location)) { return(performedAction); } objectHit.performRemoveAction(index, location); if (objectHit.type.Equals("Crafting") && objectHit.fragility != 2) { var debris1 = Game1.currentLocation.debris; int objectIndex = objectHit.bigCraftable ? -objectHit.ParentSheetIndex : objectHit.ParentSheetIndex; Vector2 toolLocation = who.GetToolLocation(false); Rectangle boundingBox = who.GetBoundingBox(); double x1 = (double)boundingBox.Center.X; boundingBox = who.GetBoundingBox(); double y1 = (double)boundingBox.Center.Y; Vector2 playerPosition = new Vector2((float)x1, (float)y1); Debris debris2 = new Debris(objectIndex, toolLocation, playerPosition); debris1.Add(debris2); } Game1.currentLocation.Objects.Remove(index); performedAction = true; } RestorePlayerExperience(snapshotPlayerExperience); } else { Game1.playSound("woodyHit"); if (location.doesTileHaveProperty(x, y, "Diggable", "Back") == null) { return(false); } location.TemporarySprites.Add(new TemporaryAnimatedSprite(12, new Vector2((float)(x * Game1.tileSize), (float)(y * Game1.tileSize)), Color.White, 8, false, 80f, 0, -1, -1f, -1, 0) { alphaFade = 0.015f }); } } else if (tool is Hoe) { var snapshotPlayerExperience = Game1.player.experiencePoints; if (location.terrainFeatures.ContainsKey(index)) { if (location.terrainFeatures[index].performToolAction(tool, 0, index, (GameLocation)null)) { location.terrainFeatures.Remove(index); performedAction = true; } } else { if (location.objects.ContainsKey(index) && location.Objects[index].performToolAction(tool, location)) { if (location.Objects[index].type.Equals("Crafting") && location.Objects[index].fragility != 2) { var debris1 = location.debris; int objectIndex = location.Objects[index].bigCraftable ? -location.Objects[index].ParentSheetIndex : location.Objects[index].ParentSheetIndex; Vector2 toolLocation = who.GetToolLocation(false); Microsoft.Xna.Framework.Rectangle boundingBox = who.GetBoundingBox(); double x1 = (double)boundingBox.Center.X; boundingBox = who.GetBoundingBox(); double y1 = (double)boundingBox.Center.Y; Vector2 playerPosition = new Vector2((float)x1, (float)y1); Debris debris2 = new Debris(objectIndex, toolLocation, playerPosition); debris1.Add(debris2); } location.Objects[index].performRemoveAction(index, location); location.Objects.Remove(index); performedAction = true; } if (location.doesTileHaveProperty((int)index.X, (int)index.Y, "Diggable", "Back") != null) { if (location.Name.Equals("UndergroundMine") && !location.isTileOccupied(index, "")) { location.terrainFeatures.Add(index, (TerrainFeature) new HoeDirt()); performedAction = true; Game1.removeSquareDebrisFromTile((int)index.X, (int)index.Y); location.checkForBuriedItem((int)index.X, (int)index.Y, false, false); location.temporarySprites.Add(new TemporaryAnimatedSprite(12, new Vector2(vector2.X * (float)Game1.tileSize, vector2.Y * (float)Game1.tileSize), Color.White, 8, Game1.random.NextDouble() < 0.5, 50f, 0, -1, -1f, -1, 0)); } else if (!location.isTileOccupied(index, "") && location.isTilePassable(new xTile.Dimensions.Location((int)index.X, (int)index.Y), Game1.viewport)) { location.makeHoeDirt(index); performedAction = true; Game1.removeSquareDebrisFromTile((int)index.X, (int)index.Y); location.temporarySprites.Add(new TemporaryAnimatedSprite(12, new Vector2(index.X * (float)Game1.tileSize, index.Y * (float)Game1.tileSize), Color.White, 8, Game1.random.NextDouble() < 0.5, 50f, 0, -1, -1f, -1, 0)); location.checkForBuriedItem((int)index.X, (int)index.Y, false, false); } } } RestorePlayerExperience(snapshotPlayerExperience); } return(performedAction); }
//this was almost entirely stolen from spacechase0 with very little contribution on my part. internal static void HandleToolTransmute(Tool tool) { int alchemyLevel = (EquivalentExchange.IsShiftKeyPressed() ? 0 : Alchemy.GetToolTransmuteRadius()); int toolLevel = tool.UpgradeLevel; //set last user to dodge a null pointer var toolPlayerFieldReflector = tool.GetType().GetField("lastUser", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); toolPlayerFieldReflector.SetValue(tool, Game1.player); Point hitLocation = GetMouseHitLocation(); GameLocation location = Game1.player.currentLocation; bool performedAction = false; //getting this out of the way, helps with easily determining tool types bool isScythe = tool is MeleeWeapon && tool.Name.ToLower().Contains("scythe"); bool isAxe = tool is StardewValley.Tools.Axe; bool isPickaxe = tool is StardewValley.Tools.Pickaxe; bool isHoe = tool is StardewValley.Tools.Hoe; bool isWateringCan = tool is StardewValley.Tools.WateringCan; for (int xOffset = -alchemyLevel; xOffset <= alchemyLevel; xOffset++) { for (int yOffset = -alchemyLevel; yOffset <= alchemyLevel; yOffset++) { if (!isScythe) { if (!IsCapableOfWithstandingToolTransmuteCost(Game1.player, 2F)) { return; } } Vector2 offsetPosition = new Vector2(xOffset + hitLocation.X, yOffset + hitLocation.Y); if (location.objects.ContainsKey(offsetPosition)) { if (isAxe || isScythe || isPickaxe || isHoe) { var snapshotPlayerExperience = Game1.player.experiencePoints; performedAction = DoToolFunction(location, Game1.player, tool, (int)offsetPosition.X, (int)offsetPosition.Y); RestorePlayerExperience(snapshotPlayerExperience); if (performedAction && !isScythe) { HandleToolTransmuteConsequence(2F); } } } else if (location.terrainFeatures.ContainsKey(offsetPosition)) { //a terrain feature, rather than a tool check, might respond to the tool TerrainFeature terrainFeature = location.terrainFeatures[offsetPosition]; //don't break stumps unless the player is in precision mode. if (terrainFeature is Tree && isAxe && (!(terrainFeature as Tree).stump || EquivalentExchange.IsShiftKeyPressed())) { Netcode.NetArray <int, Netcode.NetInt> snapshotPlayerExperience = Game1.player.experiencePoints; //trees get removed automatically performedAction = DoToolFunction(location, Game1.player, tool, (int)offsetPosition.X, (int)offsetPosition.Y); RestorePlayerExperience(snapshotPlayerExperience); if (performedAction) { HandleToolTransmuteConsequence(2F); } } else if (terrainFeature is Grass && location is Farm && isScythe) { int oldHay = (location as Farm).piecesOfHay; var snapshotPlayerExperience = Game1.player.experiencePoints; if (terrainFeature.performToolAction(tool, 0, offsetPosition, location)) { location.terrainFeatures.Remove(offsetPosition); //HandleToolTransmuteConsequence(); Scythe transmute is special and doesn't cost anything, but you don't get experience. performedAction = true; } RestorePlayerExperience(snapshotPlayerExperience); //hay get! spawn the sprite animation for acquisition of hay if (oldHay < (location as Farm).piecesOfHay) { SpawnHayAnimationSprite(location, offsetPosition, Game1.player); } } else if (terrainFeature is HoeDirt && isWateringCan && (tool as WateringCan).WaterLeft > 0) { //state of 0 is unwatered. if ((terrainFeature as HoeDirt).state != 1) { var snapshotPlayerExperience = Game1.player.experiencePoints; terrainFeature.performToolAction(tool, 0, offsetPosition, location); RestorePlayerExperience(snapshotPlayerExperience); (tool as WateringCan).WaterLeft = (tool as WateringCan).WaterLeft - 1; SpawnWateringCanAnimationSprite(location, offsetPosition); HandleToolTransmuteConsequence(2F); performedAction = true; } } else if (isPickaxe && terrainFeature is HoeDirt) { var snapshotPlayerExperience = Game1.player.experiencePoints; performedAction = DoToolFunction(location, Game1.player, tool, (int)offsetPosition.X, (int)offsetPosition.Y); RestorePlayerExperience(snapshotPlayerExperience); if (performedAction) { HandleToolTransmuteConsequence(2F); } } } else if ((isPickaxe || isAxe)) { ICollection <ResourceClump> largeResourceClusters = null; if (location is Farm) { largeResourceClusters = (NetCollection <ResourceClump>)location.GetType().GetField("resourceClumps", BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance).GetValue(location); } else if (location is MineShaft) { largeResourceClusters = (NetObjectList <ResourceClump>)location.GetType().GetField("resourceClumps", BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance).GetValue(location); } else if (location is Woods) { largeResourceClusters = (location as Woods).stumps; } DoLargeResourceClusterAction(largeResourceClusters, tool, offsetPosition, performedAction); } else if (isHoe) { var snapshotPlayerExperience = Game1.player.experiencePoints; performedAction = DoToolFunction(location, Game1.player, tool, (int)offsetPosition.X, (int)offsetPosition.Y); RestorePlayerExperience(snapshotPlayerExperience); if (performedAction) { HandleToolTransmuteConsequence(2F); } } } } if (performedAction) { SoundUtil.PlayMagickySound(); } }
public static void IncreaseTotalTransmuteValue(int transmuteValue) { EquivalentExchange.AddTotalValueTransmuted(transmuteValue); }
public static void HandleLiquidateEvent(Item heldItem, int actualValue) { //if the player is holding only one item, don't let them transmute it to money unless they're holding shift. if (heldItem.Stack == 1 && !EquivalentExchange.IsShiftKeyPressed()) { return; } //placeholder for determining if the transmute occurs, so it knows to play a sound. bool didTransmuteOccur = false; //placeholder for determining if the transmute rebounds, so it knows to play a different sound. bool didTransmuteFail = false; //if the transmute did fail, this preserves the damage so we can apply it in one cycle, otherwise batches look weird af int reboundDamageTaken = 0; //needed for some profession effects bool isItemWorthLessThanOnePercentOfMoney = (Game1.player.money * 0.01F > actualValue); //stamina cost is overridden for conduits if money is > 100x the item's value. double staminaCost = (isItemWorthLessThanOnePercentOfMoney && Game1.player.professions.Contains(Professions.Conduit)) ? 0D : Alchemy.GetStaminaCostForTransmutation(actualValue); //if the player lacks the stamina to execute a transmute, abort if (Game1.player.Stamina <= staminaCost) { return; } //if we fail this check, it's because a rebound would kill the player. //if the rebound chance is zero, this check will automatically pass. if (!Alchemy.CanSurviveRebound(actualValue, isItemWorthLessThanOnePercentOfMoney, true)) { return; } //if we fail this check, transmutation will fail this cycle. //this is our "rebound check" if (Alchemy.DidPlayerFailReboundCheck(isItemWorthLessThanOnePercentOfMoney, true)) { reboundDamageTaken += Alchemy.GetReboundDamage(actualValue); didTransmuteFail = true; } //the conduit profession makes it so that the transmutation succeeds anyway, after taking damage. if (Game1.player.professions.Contains((int)Professions.Sage) || !didTransmuteFail) { //if we reached this point transmutation will succeed didTransmuteOccur = true; Alchemy.HandleStaminaDeduction(staminaCost, false); //we floor the math here because we don't want weirdly divergent values based on stack count - the rate is fixed regardless of quantity //this occurs at the expense of rounding - liquidation is lossy. int liquidationValue = (int)Math.Floor(Alchemy.GetLiquidationValuePercentage() * actualValue); int totalValue = liquidationValue; Game1.player.Money += totalValue; Game1.player.reduceActiveItemByOne(); //the percentage of experience you get is increased by the lossiness of the transmute //as you increase in levels, this amount diminishes to a minimum of 1. double experienceValueCoefficient = 1D - Alchemy.GetLiquidationValuePercentage(); int experienceValue = (int)Math.Floor(Math.Sqrt(experienceValueCoefficient * actualValue / 10 + 1)); Alchemy.AddAlchemyExperience(experienceValue); //a transmute (at least one) happened, play the cash money sound if (didTransmuteOccur && !didTransmuteFail) { SoundUtil.PlayMoneySound(); } } //a rebound occurred, apply the damage and also play the ouchy sound. if (didTransmuteFail) { Alchemy.TakeDamageFromRebound(reboundDamageTaken); SoundUtil.PlayReboundSound(); } }