diff --git a/changelog.md b/changelog.md
index d5e716d..aeb4cd8 100644
--- a/changelog.md
+++ b/changelog.md
@@ -8,6 +8,7 @@
- Added the ability to display more things at the top of the tree tab below points.
- Made the endgame condition customizable
- Added "sell one" and "sell all" buttons for buyables.
+- Moved the old "game" to demo.js, and replaced it with a minimal game that won't cause issues when edited.
- Fixed issues with version number
- Fixed number formatting issue making things like "10e9" appear.
diff --git a/demo.html b/demo.html
new file mode 100644
index 0000000..d3381aa
--- /dev/null
+++ b/demo.html
@@ -0,0 +1,185 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Loading... (If this takes too long it means there was a serious error!)
+
+
+
+
+
{{modInfo.name}} {{VERSION.withoutName}}
+
Congratulations! You have reached the end and beaten this game, but for now...
+
Please check the Discord to see there are new content updates!
+
It took you {{formatTime(player.timePlayed)}} to beat the game.
+
Make sure that you record the time in your stream or else your speedrun won't count!
+ If you would like to speedrun this, press Play Again and record your attempt, then submit on the Discord Server in the channel #speedrun-submissions.
+
+
Oh, you are still reading this?
+
+
+
+
+
+
+
+
+
{{modInfo.name}}
+
+
{{VERSION.withName}}
+
+ The Modding Tree {{TMT_VERSION.tmtNum}} by Acamaeda
+
+ The Prestige Tree made by Jacorb and Aarex
+
+ Original idea by papyrus (on discord)
+
+
+ You have {{formatWhole(tmp[layer].baseAmt)}} {{tmp[layer].baseResource}}
+
+
+ Your best {{tmp[layer].resource}} is {{formatWhole(player[layer].best)}}
+ You have made a total of {{formatWhole(player[layer].total)}} {{tmp[layer].resource}}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/js/Demo/demoLayers.js b/js/Demo/demoLayers.js
new file mode 100644
index 0000000..28e5901
--- /dev/null
+++ b/js/Demo/demoLayers.js
@@ -0,0 +1,521 @@
+addLayer("c", {
+ layer: "c", // This is assigned automatically, both to the layer and all upgrades, etc. Shown here so you know about it
+ name: "Candies", // This is optional, only used in a few places, If absent it just uses the layer id.
+ symbol: "C", // This appears on the layer's node. Default is the id with the first letter capitalized
+ position: 0, // Horizontal position within a row. By default it uses the layer id and sorts in alphabetical order
+ startData() { return {
+ unlocked: true,
+ points: new Decimal(0),
+ best: new Decimal(0),
+ total: new Decimal(0),
+ buyables: {}, // You don't actually have to initialize this one
+ beep: false,
+ }},
+ color: "#4BDC13",
+ requires: new Decimal(10), // Can be a function that takes requirement increases into account
+ resource: "lollipops", // Name of prestige currency
+ baseResource: "candies", // Name of resource prestige is based on
+ baseAmount() {return player.points}, // Get the current amount of baseResource
+ type: "normal", // normal: cost to gain currency depends on amount gained. static: cost depends on how much you already have
+ exponent: 0.5, // Prestige currency exponent
+ base: 5, // Only needed for static layers, base of the formula (b^(x^exp))
+ roundUpCost: false, // True if the cost needs to be rounded up (use when baseResource is static?)
+ canBuyMax() {}, // Only needed for static layers with buy max
+ gainMult() { // Calculate the multiplier for main currency from bonuses
+ mult = new Decimal(1)
+ if (hasUpgrade(this.layer, 166)) mult = mult.times(2) // These upgrades don't exist
+ if (hasUpgrade(this.layer, 120)) mult = mult.times(upgradeEffect(this.layer, 120))
+ return mult
+ },
+ gainExp() { // Calculate the exponent on main currency from bonuses
+ return new Decimal(1)
+ },
+ row: 0, // Row the layer is in on the tree (0 is the first row)
+ effect() {
+ return { // Formulas for any boosts inherent to resources in the layer. Can return a single value instead of an object if there is just one effect
+ waffleBoost: (true == false ? 0 : Decimal.pow(player[this.layer].points, 0.2)),
+ icecreamCap: (player[this.layer].points * 10)
+ }},
+ effectDescription() { // Optional text to describe the effects
+ eff = this.effect();
+ eff.waffleBoost = eff.waffleBoost.times(buyableEffect(this.layer, 11).first)
+ return "which are boosting waffles by "+format(eff.waffleBoost)+" and increasing the Ice Cream cap by "+format(eff.icecreamCap)
+ },
+ milestones: {
+ 0: {requirementDescription: "3 Lollipops",
+ done() {return player[this.layer].best.gte(3)}, // Used to determine when to give the milestone
+ effectDescription: "Unlock the next milestone",
+ },
+ 1: {requirementDescription: "4 Lollipops",
+ unlocked() {return hasMilestone(this.layer, 0)},
+ done() {return player[this.layer].best.gte(4)},
+ effectDescription: "You can toggle beep and boop (which do nothing)",
+ toggles: [
+ ["c", "beep"], // Each toggle is defined by a layer and the data toggled for that layer
+ ["f", "boop"]],
+ style() {
+ if(hasMilestone(this.layer, this.id)) return {
+ 'background-color': '#1111DD'
+ }},
+
+ },
+ },
+ challenges: {
+ rows: 2,
+ cols: 12,
+ 11: {
+ name: "Fun",
+ completionLimit: 3,
+ challengeDescription() {return "Makes the game 0% harder "+challengeCompletions(this.layer, this.id) + "/" + this.completionLimit + " completions"},
+ unlocked() { return player[this.layer].best.gt(0) },
+ goal: new Decimal("20"),
+ currencyDisplayName: "lollipops", // Use if using a nonstandard currency
+ currencyInternalName: "points", // Use if using a nonstandard currency
+ currencyLayer: this.layer, // Leave empty if not in a layer
+ rewardEffect() {
+ let ret = player[this.layer].points.add(1).tetrate(0.02)
+ return ret;
+ },
+ rewardDisplay() { return format(this.rewardEffect())+"x" },
+ countsAs: [12, 21], // Use this for if a challenge includes the effects of other challenges. Being in this challenge "counts as" being in these.
+ rewardDescription: "Says hi",
+ onComplete() {console.log("hiii")} // Called when you complete the challenge
+ },
+ },
+ upgrades: {
+ rows: 2,
+ cols: 3,
+ 11: {
+ title: "Generator of Genericness",
+ description: "Gain 1 Point every second.",
+ cost: new Decimal(1),
+ unlocked() { return player[this.layer].unlocked }, // The upgrade is only visible when this is true
+ },
+ 12: {
+ description: "Candy generation is faster based on your unspent Lollipops.",
+ cost: new Decimal(1),
+ unlocked() { return (hasUpgrade(this.layer, 11))},
+ effect() { // Calculate bonuses from the upgrade. Can return a single value or an object with multiple values
+ let ret = player[this.layer].points.add(1).pow(player[this.layer].upgrades.includes(24)?1.1:(player[this.layer].upgrades.includes(14)?0.75:0.5))
+ if (ret.gte("1e20000000")) ret = ret.sqrt().times("1e10000000")
+ return ret;
+ },
+ effectDisplay() { return format(this.effect())+"x" }, // Add formatting to the effect
+ },
+ 13: {
+ description: "Unlock a secret subtab and make this layer act if you unlocked it first.",
+ cost: new Decimal(69),
+ currencyDisplayName: "candies", // Use if using a nonstandard currency
+ currencyInternalName: "points", // Use if using a nonstandard currency
+ currencyLocation: "", // The object in player data that the currency is contained in
+ unlocked() { return (hasUpgrade(this.layer, 12))},
+ onPurchase() { // This function triggers when the upgrade is purchased
+ player[this.layer].unlockOrder = 0
+ },
+ style() {
+ if (hasUpgrade(this.layer, this.id)) return {
+ 'background-color': '#1111dd'
+ }
+ else if (!canAffordUpgrade(this.layer, this.id)) {
+ return {
+ 'background-color': '#dd1111'
+ }
+ } // Otherwise use the default
+ },
+ },
+ 22: {
+ title: "This upgrade doesn't exist",
+ description: "Or does it?.",
+ currencyLocation() {return player[this.layer].buyables}, // The object in player data that the currency is contained in
+ currencyDisplayName: "exhancers", // Use if using a nonstandard currency
+ currencyInternalName: 11, // Use if using a nonstandard currency
+
+ cost: new Decimal(3),
+ unlocked() { return player[this.layer].unlocked }, // The upgrade is only visible when this is true
+ },
+ },
+ buyables: {
+ rows: 1,
+ cols: 12,
+ showRespec: true,
+ respec() { // Optional, reset things and give back your currency. Having this function makes a respec button appear
+ player[this.layer].points = player[this.layer].points.add(player[this.layer].spentOnBuyables) // A built-in thing to keep track of this but only keeps a single value
+ resetBuyables(this.layer)
+ doReset(this.layer, true) // Force a reset
+ },
+ respecText: "Respec Thingies", // Text on Respec button, optional
+ 11: {
+ title: "Exhancers", // Optional, displayed at the top in a larger font
+ cost(x=player[this.layer].buyables[this.id]) { // cost for buying xth buyable, can be an object if there are multiple currencies
+ if (x.gte(25)) x = x.pow(2).div(25)
+ let cost = Decimal.pow(2, x.pow(1.5))
+ return cost.floor()
+ },
+ effect(x=player[this.layer].buyables[this.id]) { // Effects of owning x of the items, x is a decimal
+ let eff = {}
+ if (x.gte(0)) eff.first = Decimal.pow(25, x.pow(1.1))
+ else eff.first = Decimal.pow(1/25, x.times(-1).pow(1.1))
+
+ if (x.gte(0)) eff.second = x.pow(0.8)
+ else eff.second = x.times(-1).pow(0.8).times(-1)
+ return eff;
+ },
+ display() { // Everything else displayed in the buyable button after the title
+ let data = tmp[this.layer].buyables[this.id]
+ return "Cost: " + format(data.cost) + " lollipops\n\
+ Amount: " + player[this.layer].buyables[this.id] + "\n\
+ Adds + " + format(data.effect.first) + " things and multiplies stuff by " + format(data.effect.second)
+ },
+ unlocked() { return player[this.layer].unlocked },
+ canAfford() {
+ return player[this.layer].points.gte(tmp[this.layer].buyables[this.id].cost)},
+ buy() {
+ cost = tmp[this.layer].buyables[this.id].cost
+ player[this.layer].points = player[this.layer].points.sub(cost)
+ player[this.layer].buyables[this.id] = player[this.layer].buyables[this.id].add(1)
+ player[this.layer].spentOnBuyables = player[this.layer].spentOnBuyables.add(cost) // This is a built-in system that you can use for respeccing but it only works with a single Decimal value
+ },
+ buyMax() {}, // You'll have to handle this yourself if you want
+ style: {'height':'222px'},
+ sellOne() {
+ let amount = getBuyableAmount(this.layer, this.id)
+ if (amount.lte(0)) return // Only sell one if there is at least one
+ setBuyableAmount(this.layer, this.id, amount.sub(1))
+ player[this.layer].points = player[this.layer].points.add(this.cost())
+ },
+ },
+ },
+ doReset(resettingLayer){ // Triggers when this layer is being reset, along with the layer doing the resetting. Not triggered by lower layers resetting, but is by layers on the same row.
+ if(layers[resettingLayer].row > this.row) layerDataReset(this.layer, ["upgrades", "challenges"]) // This is actually the default behavior
+ },
+ layerShown() {return true}, // Condition for when layer appears on the tree
+ automate() {
+ }, // Do any automation inherent to this layer if appropriate
+ resetsNothing() {return false},
+ onPrestige(gain) {
+ return
+ }, // Useful for if you gain secondary resources or have other interesting things happen to this layer when you reset it. You gain the currency after this function ends.
+
+ hotkeys: [
+ {key: "c", description: "C: reset for lollipops or whatever", onPress(){if (canReset(this.layer)) doReset(this.layer)}},
+ {key: "ctrl+c", description: "Ctrl+c: respec things", onPress(){if (player[this.layer].unlocked) respecBuyables(this.layer)}},
+ ],
+ increaseUnlockOrder: [], // Array of layer names to have their order increased when this one is first unlocked
+
+ microtabs: {
+ stuff: {
+ first: {
+ content: ["upgrades", ["display-text", function() {return "confirmed"}]]
+ },
+ second: {
+ content: [["upgrade", 11],
+ ["row", [["upgrade", 11], "blank", "blank", ["upgrade", 11],]],
+
+ ["display-text", function() {return "double confirmed"}]]
+ },
+ },
+ otherStuff: {
+ // There could be another set of microtabs here
+ }
+ },
+
+ bars: {
+ longBoi: {
+ fillStyle: {'background-color' : "#FFFFFF"},
+ baseStyle: {'background-color' : "#696969"},
+ textStyle: {'color': '#04e050'},
+
+ borderStyle() {return {}},
+ direction: RIGHT,
+ width: 300,
+ height: 30,
+ progress() {
+ return (player.points.log(10).div(10)).toNumber()
+ },
+ display() {
+ return format(player.points) + " / 1e10 points"
+ },
+ unlocked: true,
+
+ },
+ tallBoi: {
+ fillStyle: {'background-color' : "#4BEC13"},
+ baseStyle: {'background-color' : "#000000"},
+ textStyle: {'text-shadow': '0px 0px 2px #000000'},
+
+ borderStyle() {return {'border-width': "7px"}},
+ direction: UP,
+ width: 50,
+ height: 200,
+ progress() {
+ return player.points.div(100)
+ },
+ display() {
+ return formatWhole((player.points.div(1)).min(100)) + "%"
+ },
+ unlocked: true,
+
+ },
+ flatBoi: {
+ fillStyle: {'background-color' : "#FE0102"},
+ baseStyle: {'background-color' : "#222222"},
+ textStyle: {'text-shadow': '0px 0px 2px #000000'},
+
+ borderStyle() {return {}},
+ direction: UP,
+ width: 100,
+ height: 30,
+ progress() {
+ return player.c.points.div(50)
+ },
+ unlocked: true,
+
+ },
+ },
+
+ // Optional, lets you format the tab yourself by listing components. You can create your own components in v.js.
+ tabFormat: {
+ "main tab": {
+ buttonStyle() {return {'color': 'orange'}},
+ content:
+ ["main-display",
+ "prestige-button",
+ ["blank", "5px"], // Height
+ ["raw-html", function() {return ""}],
+ ["display-text",
+ function() {return 'I have ' + format(player.points) + ' pointy points!'},
+ {"color": "red", "font-size": "32px", "font-family": "Comic Sans MS"}],
+ "h-line", "milestones", "blank", "upgrades", "challenges"],
+ },
+ thingies: {
+ style() {return {'background-color': '#222222'}},
+ buttonStyle() {return {'border-color': 'orange'}},
+ content:[
+ ["buyables", ""], "blank",
+ ["row", [
+ ["toggle", ["c", "beep"]], ["blank", ["30px", "10px"]], // Width, height
+ ["display-text", function() {return "Beep"}], "blank", ["v-line", "200px"],
+ ["column", [
+ ["prestige-button", "", {'width': '150px', 'height': '80px'}],
+ ["prestige-button", "", {'width': '100px', 'height': '150px'}],
+ ]],
+ ], {'width': '600px', 'height': '350px', 'background-color': 'green', 'border-style': 'solid'}],
+ "blank",
+ ["display-image", "discord.png"],],
+ },
+ jail: {
+ content: [
+ ["bar", "longBoi"], "blank",
+ ["row", [
+ ["column", [
+ ["display-text", "Sugar level:", {'color': 'teal'}], "blank", ["bar", "tallBoi"]],
+ {'background-color': '#555555', 'padding': '15px'}],
+ "blank",
+ ["column", [
+ ["display-text", "idk"],
+ ["blank", ['0', '50px']], ["bar", "flatBoi"]
+ ]],
+ ]],
+ "blank", ["display-text", "It's jail because \"bars\"! So funny! Ha ha!"],
+ ],
+ },
+ illuminati: {
+ unlocked() {return (hasUpgrade("c", 13))},
+ content:[
+ ["raw-html", function() {return "
C O N F I R M E D
"}], "blank",
+ ["microtabs", "stuff", {'width': '600px', 'height': '350px', 'background-color': 'brown', 'border-style': 'solid'}]
+ ]
+ }
+
+ },
+ style() {return {
+ //'background-color': '#3325CC'
+ }},
+ nodeStyle() {return { // Style on the layer node
+ 'color': '#3325CC',
+ 'text-decoration': 'underline'
+ }},
+ componentStyles: {
+ "challenge"() {return {'height': '200px'}},
+ "prestige-button"() {return {'color': '#AA66AA'}},
+ },
+ tooltip() { // Optional, tooltip displays when the layer is unlocked
+ let tooltip = formatWhole(player[this.layer].points) + " " + this.resource
+ if (player[this.layer].buyables[11].gt(0)) tooltip += "\n" + formatWhole(player[this.layer].buyables[11]) + " Exhancers"
+ return tooltip
+ },
+ shouldNotify() { // Optional, layer will be highlighted on the tree if true.
+ // Layer will automatically highlight if an upgrade is purchasable.
+ return (player.c.buyables[11] == 1)
+ },
+ resetDescription: "Melt your points into ",
+})
+
+// This layer is mostly minimal but it uses a custom prestige type and a clickable
+addLayer("f", {
+ startData() { return {
+ unlocked: false,
+ points: new Decimal(0),
+ boop: false,
+ clickables: {[11]: "Start"} // Optional default Clickable state
+ }},
+ color: "#FE0102",
+ requires() {return new Decimal(10)},
+ resource: "farm points",
+ baseResource: "candies",
+ baseAmount() {return player.points},
+ type: "custom", // A "Custom" type which is effectively static
+ exponent: 0.5,
+ base: 3,
+ roundUpCost: true,
+ canBuyMax() {return hasAchievement('a', 13)},
+ gainMult() {
+ return new Decimal(1)
+ },
+ gainExp() {
+ return new Decimal(1)
+ },
+ row: 1,
+ layerShown() {return true},
+ branches: ["c"], // When this layer appears, a branch will appear from this layer to any layers here. Each entry can be a pair consisting of a layer id and a color.
+
+ tooltipLocked() { // Optional, tooltip displays when the layer is locked
+ return ("This weird farmer dinosaur will only see you if you have at least " + this.requires() + " candies. You only have " + formatWhole(player.points))
+ },
+
+ midsection: [
+ "blank", ['display-image', 'https://images.beano.com/store/24ab3094eb95e5373bca1ccd6f330d4406db8d1f517fc4170b32e146f80d?auto=compress%2Cformat&dpr=1&w=390'],
+ ["display-text", "Bork bork!"]
+ ],
+
+ // The following are only currently used for "custom" Prestige type:
+ prestigeButtonText() { //Is secretly HTML
+ if (!this.canBuyMax()) return "Hi! I'm a weird dinosaur and I'll give you a Farm Point in exchange for all of your candies and lollipops! (At least " + formatWhole(tmp[this.layer].nextAt) + " candies)"
+ if (this.canBuyMax()) return "Hi! I'm a weird dinosaur and I'll give you " + formatWhole(tmp[this.layer].resetGain) + " Farm Points in exchange for all of your candies and lollipops! (You'll get another one at " + formatWhole(tmp[layer].nextAtDisp) + " candies)"
+ },
+ getResetGain() {
+ return getResetGain(this.layer, useType = "static")
+ },
+ getNextAt(canMax=false) { //
+ return getNextAt(this.layer, canMax, useType = "static")
+ },
+ canReset() {
+ return tmp[this.layer].baseAmount.gte(tmp[this.layer].nextAt)
+ },
+ // This is also non minimal, a Clickable!
+ clickables: {
+ rows: 1,
+ cols: 1,
+ masterButtonPress() { // Optional, reset things and give back your currency. Having this function makes a respec button appear
+ if (getClickableState(this.layer, 11) == "Borkened...")
+ player[this.layer].clickables[11] = "Start"
+ },
+ masterButtonText() {return (getClickableState(this.layer, 11) == "Borkened...") ? "Fix the clickable!" : "Does nothing"}, // Text on Respec button, optional
+ 11: {
+ title: "Clicky clicky!", // Optional, displayed at the top in a larger font
+ display() { // Everything else displayed in the buyable button after the title
+ let data = getClickableState(this.layer, this.id)
+ return "Current state: " + data
+ },
+ unlocked() { return player[this.layer].unlocked },
+ canClick() {
+ return getClickableState(this.layer, this.id) !== "Borkened..."},
+ onClick() {
+ switch(getClickableState(this.layer, this.id)){
+ case "Start":
+ player[this.layer].clickables[this.id] = "A new state!"
+ break;
+ case "A new state!":
+ player[this.layer].clickables[this.id] = "Keep going!"
+ break;
+ case "Keep going!":
+ player[this.layer].clickables[this.id] = "Maybe that's a bit too far..."
+ break;
+ case "Maybe that's a bit too far...":
+ player[this.layer].clickables[this.id] = "Borkened..."
+ break;
+ default:
+ player[this.layer].clickables[this.id] = "Start"
+ break;
+
+ }
+ },
+ style() {
+ switch(getClickableState(this.layer, this.id)){
+ case "Start":
+ return {'background-color': 'green'}
+ break;
+ case "A new state!":
+ return {'background-color': 'yellow'}
+ break;
+ case "Keep going!":
+ return {'background-color': 'orange'}
+ break;
+ case "Maybe that's a bit too far...":
+ return {'background-color': 'red'}
+ break;
+ default:
+ return {}
+ break;
+ }},
+ },
+ },
+
+},
+)
+
+// A side layer with achievements
+addLayer("a", {
+ startData() { return {
+ unlocked: true,
+ points: new Decimal(0),
+ }},
+ color: "yellow",
+ resource: "achievement power",
+ type: "none",
+ row: "side",
+ layerShown() {return true},
+ tooltip() { // Optional, tooltip displays when the layer is locked
+ return ("Achievements")
+ },
+ achievements: {
+ rows: 2,
+ cols: 3,
+ 11: {
+ name: "Get me!",
+ done() {return true}, // This one is a freebie
+ goalTooltip: "How did this happen?", // Shows when achievement is not completed
+ doneTooltip: "You did it!", // Showed when the achievement is completed
+ },
+ 12: {
+ name: "Impossible!",
+ done() {return false},
+ goalTooltip: "Mwahahaha!", // Shows when achievement is not completed
+ doneTooltip: "HOW????", // Showed when the achievement is completed
+ },
+ 13: {
+ name: "EIEIO",
+ done() {return player.f.points.gte(1)},
+ tooltip: "Get a farm point.\n\nReward: The dinosaur is now your friend (you can max Farm Points).", // Showed when the achievement is completed
+ onComplete() {console.log("Bork bork bork!")}
+ },
+ },
+ midsection: [
+ "achievements",
+ ]
+ },
+)
+
+
+// A "ghost" layer which offsets f in the tree
+addLayer("spook", {
+ startData() { return {
+ unlocked: true,
+ points: new Decimal(0),
+ }},
+ type: "none",
+ row: 1,
+ layerShown: "ghost",
+},
+)
+
diff --git a/js/Demo/demoMod.js b/js/Demo/demoMod.js
new file mode 100644
index 0000000..3baf90e
--- /dev/null
+++ b/js/Demo/demoMod.js
@@ -0,0 +1,66 @@
+let modInfo = {
+ name: "The Modding Tree",
+ id: "modbase",
+ pointsName: "points",
+ discordName: "",
+ discordLink: "",
+ changelogLink: "https://github.com/Acamaeda/The-Modding-Tree/blob/master/changelog.md",
+ offlineLimit: 1, // In hours
+ initialStartPoints: new Decimal (10) // Used for hard resets and new players
+}
+
+// Set your version in num and name
+let VERSION = {
+ num: "2.1",
+ name: " We should have thought of this sooner!",
+}
+
+// If you add new functions anywhere inside of a layer, and those functions have an effect when called, add them here.
+// (The ones here are examples, all official functions are already taken care of)
+var doNotCallTheseFunctionsEveryTick = ["doReset", "buy", "onPurchase", "blowUpEverything"]
+
+function getStartPoints(){
+ return new Decimal(modInfo.initialStartPoints)
+}
+
+// Determines if it should show points/sec
+function canGenPoints(){
+ return hasUpgrade("c", 11)
+}
+
+// Calculate points/sec!
+function getPointGen() {
+ if(!canGenPoints())
+ return new Decimal(0)
+
+ let gain = new Decimal(1)
+ if (hasUpgrade("c", 12)) gain = gain.times(upgradeEffect("c", 12))
+ return gain
+}
+
+// You can add non-layer related variables that should to into "player" and be saved here, along with default values
+function addedPlayerData() { return {
+ weather: "Yes",
+ happiness: new Decimal(72),
+}}
+
+// Display extra things at the top of the page
+var displayThings = [
+ function() {if (player.points.eq(69)) return "Tee hee!"},
+ function() {if (player.f.points.gt(1)) return `You have ${player.f.points} farm points. (Which do nothing.)`},
+ function() {if (inChallenge("c", 11)) return "The game is currently
0%
harder."},
+]
+
+// Determines when the game "ends"
+function isEndgame() {
+ return player.points.gte(new Decimal("e280000000"))
+}
+
+
+
+// Less important things beyond this point!
+
+// You can change this if you have things that can be messed up by long tick lengths
+function maxTickLength() {
+ return(3600000) // Default is 1 hour which is just arbitrarily large
+}
\ No newline at end of file
diff --git a/js/game.js b/js/game.js
index 2a845e3..8880a40 100644
--- a/js/game.js
+++ b/js/game.js
@@ -107,7 +107,6 @@ function layerDataReset(layer, keep = []) {
if (player[layer][keep[thing]] !== undefined)
storedData[keep[thing]] = player[layer][keep[thing]]
}
- console.log(storedData)
player[layer] = layers[layer].startData();
player[layer].upgrades = []
diff --git a/js/layers.js b/js/layers.js
index 43c148c..31e3336 100644
--- a/js/layers.js
+++ b/js/layers.js
@@ -1,523 +1,28 @@
-addLayer("c", {
- layer: "c", // This is assigned automatically, both to the layer and all upgrades, etc. Shown here so you know about it
- name: "Candies", // This is optional, only used in a few places, If absent it just uses the layer id.
- symbol: "C", // This appears on the layer's node. Default is the id with the first letter capitalized
+addLayer("p", {
+ name: "prestige", // This is optional, only used in a few places, If absent it just uses the layer id.
+ symbol: "P", // This appears on the layer's node. Default is the id with the first letter capitalized
position: 0, // Horizontal position within a row. By default it uses the layer id and sorts in alphabetical order
startData() { return {
unlocked: true,
points: new Decimal(0),
- best: new Decimal(0),
- total: new Decimal(0),
- buyables: {}, // You don't actually have to initialize this one
- beep: false,
}},
color: "#4BDC13",
requires: new Decimal(10), // Can be a function that takes requirement increases into account
- resource: "lollipops", // Name of prestige currency
- baseResource: "candies", // Name of resource prestige is based on
+ resource: "prestige points", // Name of prestige currency
+ baseResource: "points", // Name of resource prestige is based on
baseAmount() {return player.points}, // Get the current amount of baseResource
type: "normal", // normal: cost to gain currency depends on amount gained. static: cost depends on how much you already have
exponent: 0.5, // Prestige currency exponent
- base: 5, // Only needed for static layers, base of the formula (b^(x^exp))
- roundUpCost: false, // True if the cost needs to be rounded up (use when baseResource is static?)
- canBuyMax() {}, // Only needed for static layers with buy max
gainMult() { // Calculate the multiplier for main currency from bonuses
mult = new Decimal(1)
- if (hasUpgrade(this.layer, 166)) mult = mult.times(2) // These upgrades don't exist
- if (hasUpgrade(this.layer, 120)) mult = mult.times(upgradeEffect(this.layer, 120))
return mult
},
gainExp() { // Calculate the exponent on main currency from bonuses
return new Decimal(1)
},
row: 0, // Row the layer is in on the tree (0 is the first row)
- effect() {
- return { // Formulas for any boosts inherent to resources in the layer. Can return a single value instead of an object if there is just one effect
- waffleBoost: (true == false ? 0 : Decimal.pow(player[this.layer].points, 0.2)),
- icecreamCap: (player[this.layer].points * 10)
- }},
- effectDescription() { // Optional text to describe the effects
- eff = this.effect();
- eff.waffleBoost = eff.waffleBoost.times(buyableEffect(this.layer, 11).first)
- return "which are boosting waffles by "+format(eff.waffleBoost)+" and increasing the Ice Cream cap by "+format(eff.icecreamCap)
- },
- milestones: {
- 0: {requirementDescription: "3 Lollipops",
- done() {return player[this.layer].best.gte(3)}, // Used to determine when to give the milestone
- effectDescription: "Unlock the next milestone",
- },
- 1: {requirementDescription: "4 Lollipops",
- unlocked() {return hasMilestone(this.layer, 0)},
- done() {return player[this.layer].best.gte(4)},
- effectDescription: "You can toggle beep and boop (which do nothing)",
- toggles: [
- ["c", "beep"], // Each toggle is defined by a layer and the data toggled for that layer
- ["f", "boop"]],
- style() {
- if(hasMilestone(this.layer, this.id)) return {
- 'background-color': '#1111DD'
- }},
-
- },
- },
- challenges: {
- rows: 2,
- cols: 12,
- 11: {
- name: "Fun",
- completionLimit: 3,
- challengeDescription() {return "Makes the game 0% harder "+challengeCompletions(this.layer, this.id) + "/" + this.completionLimit + " completions"},
- unlocked() { return player[this.layer].best.gt(0) },
- goal: new Decimal("20"),
- currencyDisplayName: "lollipops", // Use if using a nonstandard currency
- currencyInternalName: "points", // Use if using a nonstandard currency
- currencyLayer: this.layer, // Leave empty if not in a layer
- rewardEffect() {
- let ret = player[this.layer].points.add(1).tetrate(0.02)
- return ret;
- },
- rewardDisplay() { return format(this.rewardEffect())+"x" },
- countsAs: [12, 21], // Use this for if a challenge includes the effects of other challenges. Being in this challenge "counts as" being in these.
- rewardDescription: "Says hi",
- onComplete() {console.log("hiii")} // Called when you complete the challenge
- },
- },
- upgrades: {
- rows: 2,
- cols: 3,
- 11: {
- title: "Generator of Genericness",
- description: "Gain 1 Point every second.",
- cost: new Decimal(1),
- unlocked() { return player[this.layer].unlocked }, // The upgrade is only visible when this is true
- },
- 12: {
- description: "Candy generation is faster based on your unspent Lollipops.",
- cost: new Decimal(1),
- unlocked() { return (hasUpgrade(this.layer, 11))},
- effect() { // Calculate bonuses from the upgrade. Can return a single value or an object with multiple values
- let ret = player[this.layer].points.add(1).pow(player[this.layer].upgrades.includes(24)?1.1:(player[this.layer].upgrades.includes(14)?0.75:0.5))
- if (ret.gte("1e20000000")) ret = ret.sqrt().times("1e10000000")
- return ret;
- },
- effectDisplay() { return format(this.effect())+"x" }, // Add formatting to the effect
- },
- 13: {
- description: "Unlock a secret subtab and make this layer act if you unlocked it first.",
- cost: new Decimal(69),
- currencyDisplayName: "candies", // Use if using a nonstandard currency
- currencyInternalName: "points", // Use if using a nonstandard currency
- currencyLocation: "", // The object in player data that the currency is contained in
- unlocked() { return (hasUpgrade(this.layer, 12))},
- onPurchase() { // This function triggers when the upgrade is purchased
- player[this.layer].unlockOrder = 0
- },
- style() {
- if (hasUpgrade(this.layer, this.id)) return {
- 'background-color': '#1111dd'
- }
- else if (!canAffordUpgrade(this.layer, this.id)) {
- return {
- 'background-color': '#dd1111'
- }
- } // Otherwise use the default
- },
- },
- 22: {
- title: "This upgrade doesn't exist",
- description: "Or does it?.",
- currencyLocation() {return player[this.layer].buyables}, // The object in player data that the currency is contained in
- currencyDisplayName: "exhancers", // Use if using a nonstandard currency
- currencyInternalName: 11, // Use if using a nonstandard currency
-
- cost: new Decimal(3),
- unlocked() { return player[this.layer].unlocked }, // The upgrade is only visible when this is true
- },
- },
- buyables: {
- rows: 1,
- cols: 12,
- showRespec: true,
- respec() { // Optional, reset things and give back your currency. Having this function makes a respec button appear
- player[this.layer].points = player[this.layer].points.add(player[this.layer].spentOnBuyables) // A built-in thing to keep track of this but only keeps a single value
- resetBuyables(this.layer)
- doReset(this.layer, true) // Force a reset
- },
- respecText: "Respec Thingies", // Text on Respec button, optional
- 11: {
- title: "Exhancers", // Optional, displayed at the top in a larger font
- cost(x=player[this.layer].buyables[this.id]) { // cost for buying xth buyable, can be an object if there are multiple currencies
- if (x.gte(25)) x = x.pow(2).div(25)
- let cost = Decimal.pow(2, x.pow(1.5))
- return cost.floor()
- },
- effect(x=player[this.layer].buyables[this.id]) { // Effects of owning x of the items, x is a decimal
- let eff = {}
- if (x.gte(0)) eff.first = Decimal.pow(25, x.pow(1.1))
- else eff.first = Decimal.pow(1/25, x.times(-1).pow(1.1))
-
- if (x.gte(0)) eff.second = x.pow(0.8)
- else eff.second = x.times(-1).pow(0.8).times(-1)
- return eff;
- },
- display() { // Everything else displayed in the buyable button after the title
- let data = tmp[this.layer].buyables[this.id]
- return "Cost: " + format(data.cost) + " lollipops\n\
- Amount: " + player[this.layer].buyables[this.id] + "\n\
- Adds + " + format(data.effect.first) + " things and multiplies stuff by " + format(data.effect.second)
- },
- unlocked() { return player[this.layer].unlocked },
- canAfford() {
- return player[this.layer].points.gte(tmp[this.layer].buyables[this.id].cost)},
- buy() {
- cost = tmp[this.layer].buyables[this.id].cost
- player[this.layer].points = player[this.layer].points.sub(cost)
- player[this.layer].buyables[this.id] = player[this.layer].buyables[this.id].add(1)
- player[this.layer].spentOnBuyables = player[this.layer].spentOnBuyables.add(cost) // This is a built-in system that you can use for respeccing but it only works with a single Decimal value
- },
- buyMax() {}, // You'll have to handle this yourself if you want
- style: {'height':'222px'},
- sellOne() {
- let amount = getBuyableAmount(this.layer, this.id)
- if (amount.lte(0)) return // Only sell one if there is at least one
- setBuyableAmount(this.layer, this.id, amount.sub(1))
- player[this.layer].points = player[this.layer].points.add(this.cost())
- },
- },
- },
- doReset(resettingLayer){ // Triggers when this layer is being reset, along with the layer doing the resetting. Not triggered by lower layers resetting, but is by layers on the same row.
- if(layers[resettingLayer].row > this.row) layerDataReset(this.layer, ["upgrades", "challenges"]) // This is actually the default behavior
- },
- layerShown() {return true}, // Condition for when layer appears on the tree
- automate() {
- }, // Do any automation inherent to this layer if appropriate
- resetsNothing() {return false},
- onPrestige(gain) {
- return
- }, // Useful for if you gain secondary resources or have other interesting things happen to this layer when you reset it. You gain the currency after this function ends.
-
hotkeys: [
- {key: "c", description: "C: reset for lollipops or whatever", onPress(){if (canReset(this.layer)) doReset(this.layer)}},
- {key: "ctrl+c", description: "Ctrl+c: respec things", onPress(){if (player[this.layer].unlocked) respecBuyables(this.layer)}},
+ {key: "p", description: "Reset for prestige points", onPress(){if (canReset(this.layer)) doReset(this.layer)}},
],
- increaseUnlockOrder: [], // Array of layer names to have their order increased when this one is first unlocked
-
- microtabs: {
- stuff: {
- first: {
- content: ["upgrades", ["display-text", function() {return "confirmed"}]]
- },
- second: {
- content: [["upgrade", 11],
- ["row", [["upgrade", 11], "blank", "blank", ["upgrade", 11],]],
-
- ["display-text", function() {return "double confirmed"}]]
- },
- },
- otherStuff: {
- // There could be another set of microtabs here
- }
- },
-
- bars: {
- longBoi: {
- fillStyle: {'background-color' : "#FFFFFF"},
- baseStyle: {'background-color' : "#696969"},
- textStyle: {'color': '#04e050'},
-
- borderStyle() {return {}},
- direction: RIGHT,
- width: 300,
- height: 30,
- progress() {
- return (player.points.log(10).div(10)).toNumber()
- },
- display() {
- return format(player.points) + " / 1e10 points"
- },
- unlocked: true,
-
- },
- tallBoi: {
- fillStyle: {'background-color' : "#4BEC13"},
- baseStyle: {'background-color' : "#000000"},
- textStyle: {'text-shadow': '0px 0px 2px #000000'},
-
- borderStyle() {return {'border-width': "7px"}},
- direction: UP,
- width: 50,
- height: 200,
- progress() {
- return player.points.div(100)
- },
- display() {
- return formatWhole((player.points.div(1)).min(100)) + "%"
- },
- unlocked: true,
-
- },
- flatBoi: {
- fillStyle: {'background-color' : "#FE0102"},
- baseStyle: {'background-color' : "#222222"},
- textStyle: {'text-shadow': '0px 0px 2px #000000'},
-
- borderStyle() {return {}},
- direction: UP,
- width: 100,
- height: 30,
- progress() {
- return player.c.points.div(50)
- },
- unlocked: true,
-
- },
- },
-
- // Optional, lets you format the tab yourself by listing components. You can create your own components in v.js.
- tabFormat: {
- "main tab": {
- buttonStyle() {return {'color': 'orange'}},
- content:
- ["main-display",
- "prestige-button",
- ["blank", "5px"], // Height
- ["raw-html", function() {return ""}],
- ["display-text",
- function() {return 'I have ' + format(player.points) + ' pointy points!'},
- {"color": "red", "font-size": "32px", "font-family": "Comic Sans MS"}],
- "h-line", "milestones", "blank", "upgrades", "challenges"],
- },
- thingies: {
- style() {return {'background-color': '#222222'}},
- buttonStyle() {return {'border-color': 'orange'}},
- content:[
- ["buyables", ""], "blank",
- ["row", [
- ["toggle", ["c", "beep"]], ["blank", ["30px", "10px"]], // Width, height
- ["display-text", function() {return "Beep"}], "blank", ["v-line", "200px"],
- ["column", [
- ["prestige-button", "", {'width': '150px', 'height': '80px'}],
- ["prestige-button", "", {'width': '100px', 'height': '150px'}],
- ]],
- ], {'width': '600px', 'height': '350px', 'background-color': 'green', 'border-style': 'solid'}],
- "blank",
- ["display-image", "discord.png"],],
- },
- jail: {
- content: [
- ["bar", "longBoi"], "blank",
- ["row", [
- ["column", [
- ["display-text", "Sugar level:", {'color': 'teal'}], "blank", ["bar", "tallBoi"]],
- {'background-color': '#555555', 'padding': '15px'}],
- "blank",
- ["column", [
- ["display-text", "idk"],
- ["blank", ['0', '50px']], ["bar", "flatBoi"]
- ]],
- ]],
- "blank", ["display-text", "It's jail because \"bars\"! So funny! Ha ha!"],
- ],
- },
- illuminati: {
- unlocked() {return (hasUpgrade("c", 13))},
- content:[
- ["raw-html", function() {return "
C O N F I R M E D
"}], "blank",
- ["microtabs", "stuff", {'width': '600px', 'height': '350px', 'background-color': 'brown', 'border-style': 'solid'}]
- ]
- }
-
- },
- style() {return {
- //'background-color': '#3325CC'
- }},
- nodeStyle() {return { // Style on the layer node
- 'color': '#3325CC',
- 'text-decoration': 'underline'
- }},
- componentStyles: {
- "challenge"() {return {'height': '200px'}},
- "prestige-button"() {return {'color': '#AA66AA'}},
- },
- tooltip() { // Optional, tooltip displays when the layer is unlocked
- let tooltip = formatWhole(player[this.layer].points) + " " + this.resource
- if (player[this.layer].buyables[11].gt(0)) tooltip += "\n" + formatWhole(player[this.layer].buyables[11]) + " Exhancers"
- return tooltip
- },
- shouldNotify() { // Optional, layer will be highlighted on the tree if true.
- // Layer will automatically highlight if an upgrade is purchasable.
- return (player.c.buyables[11] == 1)
- },
- resetDescription: "Melt your points into ",
-})
-
-// This layer is mostly minimal but it uses a custom prestige type and a clickable
-addLayer("f", {
- startData() { return {
- unlocked: false,
- points: new Decimal(0),
- boop: false,
- clickables: {[11]: "Start"} // Optional default Clickable state
- }},
- color: "#FE0102",
- requires() {return new Decimal(10)},
- resource: "farm points",
- baseResource: "candies",
- baseAmount() {return player.points},
- type: "custom", // A "Custom" type which is effectively static
- exponent: 0.5,
- base: 3,
- roundUpCost: true,
- canBuyMax() {return hasAchievement('a', 13)},
- gainMult() {
- return new Decimal(1)
- },
- gainExp() {
- return new Decimal(1)
- },
- row: 1,
- layerShown() {return true},
- branches: ["c"], // When this layer appears, a branch will appear from this layer to any layers here. Each entry can be a pair consisting of a layer id and a color.
-
- tooltipLocked() { // Optional, tooltip displays when the layer is locked
- return ("This weird farmer dinosaur will only see you if you have at least " + this.requires() + " candies. You only have " + formatWhole(player.points))
- },
-
- midsection: [
- "blank", ['display-image', 'https://images.beano.com/store/24ab3094eb95e5373bca1ccd6f330d4406db8d1f517fc4170b32e146f80d?auto=compress%2Cformat&dpr=1&w=390'],
- ["display-text", "Bork bork!"]
- ],
-
- // The following are only currently used for "custom" Prestige type:
- prestigeButtonText() { //Is secretly HTML
- if (!this.canBuyMax()) return "Hi! I'm a weird dinosaur and I'll give you a Farm Point in exchange for all of your candies and lollipops! (At least " + formatWhole(tmp[this.layer].nextAt) + " candies)"
- if (this.canBuyMax()) return "Hi! I'm a weird dinosaur and I'll give you " + formatWhole(tmp[this.layer].resetGain) + " Farm Points in exchange for all of your candies and lollipops! (You'll get another one at " + formatWhole(tmp[layer].nextAtDisp) + " candies)"
- },
- getResetGain() {
- return getResetGain(this.layer, useType = "static")
- },
- getNextAt(canMax=false) { //
- return getNextAt(this.layer, canMax, useType = "static")
- },
- canReset() {
- return tmp[this.layer].baseAmount.gte(tmp[this.layer].nextAt)
- },
- // This is also non minimal, a Clickable!
- clickables: {
- rows: 1,
- cols: 1,
- masterButtonPress() { // Optional, reset things and give back your currency. Having this function makes a respec button appear
- if (getClickableState(this.layer, 11) == "Borkened...")
- player[this.layer].clickables[11] = "Start"
- },
- masterButtonText() {return (getClickableState(this.layer, 11) == "Borkened...") ? "Fix the clickable!" : "Does nothing"}, // Text on Respec button, optional
- 11: {
- title: "Clicky clicky!", // Optional, displayed at the top in a larger font
- display() { // Everything else displayed in the buyable button after the title
- let data = getClickableState(this.layer, this.id)
- return "Current state: " + data
- },
- unlocked() { return player[this.layer].unlocked },
- canClick() {
- return getClickableState(this.layer, this.id) !== "Borkened..."},
- onClick() {
- switch(getClickableState(this.layer, this.id)){
- case "Start":
- player[this.layer].clickables[this.id] = "A new state!"
- break;
- case "A new state!":
- player[this.layer].clickables[this.id] = "Keep going!"
- break;
- case "Keep going!":
- player[this.layer].clickables[this.id] = "Maybe that's a bit too far..."
- break;
- case "Maybe that's a bit too far...":
- player[this.layer].clickables[this.id] = "Borkened..."
- break;
- default:
- player[this.layer].clickables[this.id] = "Start"
- break;
-
- }
- },
- style() {
- switch(getClickableState(this.layer, this.id)){
- case "Start":
- return {'background-color': 'green'}
- break;
- case "A new state!":
- return {'background-color': 'yellow'}
- break;
- case "Keep going!":
- return {'background-color': 'orange'}
- break;
- case "Maybe that's a bit too far...":
- return {'background-color': 'red'}
- break;
- default:
- return {}
- break;
- }},
- },
- },
-
-},
-)
-
-// This layer is mostly minimal but it uses a custom prestige type and a clickable
-addLayer("a", {
- startData() { return {
- unlocked: true,
- points: new Decimal(0),
- }},
- color: "yellow",
- resource: "achievement power",
- type: "none",
- row: "side",
- layerShown() {return true},
- tooltip() { // Optional, tooltip displays when the layer is locked
- return ("Achievements")
- },
- achievements: {
- rows: 2,
- cols: 3,
- 11: {
- name: "Get me!",
- done() {return true}, // This one is a freebie
- goalTooltip: "How did this happen?", // Shows when achievement is not completed
- doneTooltip: "You did it!", // Showed when the achievement is completed
- },
- 12: {
- name: "Impossible!",
- done() {return false},
- goalTooltip: "Mwahahaha!", // Shows when achievement is not completed
- doneTooltip: "HOW????", // Showed when the achievement is completed
- },
- 13: {
- name: "EIEIO",
- done() {return player.f.points.gte(1)},
- tooltip: "Get a farm point.\n\nReward: The dinosaur is now your friend (you can max Farm Points).", // Showed when the achievement is completed
- onComplete() {console.log("Bork bork bork!")}
- },
- },
- midsection: [
- "achievements",
- ]
- },
-)
-
-
-// This layer is mostly minimal but it uses a custom prestige type and a clickable
-addLayer("spook", {
- startData() { return {
- unlocked: true,
- points: new Decimal(0),
- }},
- color: "yellow",
- resource: "achievement power",
- type: "none",
- row: 1,
- layerShown: "ghost",
-},
-)
-
+ layerShown(){return true},
+})
\ No newline at end of file
diff --git a/js/mod.js b/js/mod.js
index 3baf90e..12f2f21 100644
--- a/js/mod.js
+++ b/js/mod.js
@@ -1,6 +1,6 @@
let modInfo = {
- name: "The Modding Tree",
- id: "modbase",
+ name: "The Demo Tree",
+ id: "moddemo",
pointsName: "points",
discordName: "",
discordLink: "",
@@ -11,13 +11,13 @@ let modInfo = {
// Set your version in num and name
let VERSION = {
- num: "2.1",
- name: " We should have thought of this sooner!",
+ num: "0.0",
+ name: "Literally nothing",
}
// If you add new functions anywhere inside of a layer, and those functions have an effect when called, add them here.
// (The ones here are examples, all official functions are already taken care of)
-var doNotCallTheseFunctionsEveryTick = ["doReset", "buy", "onPurchase", "blowUpEverything"]
+var doNotCallTheseFunctionsEveryTick = ["blowUpEverything"]
function getStartPoints(){
return new Decimal(modInfo.initialStartPoints)
@@ -25,7 +25,7 @@ function getStartPoints(){
// Determines if it should show points/sec
function canGenPoints(){
- return hasUpgrade("c", 11)
+ return true
}
// Calculate points/sec!
@@ -34,21 +34,15 @@ function getPointGen() {
return new Decimal(0)
let gain = new Decimal(1)
- if (hasUpgrade("c", 12)) gain = gain.times(upgradeEffect("c", 12))
return gain
}
// You can add non-layer related variables that should to into "player" and be saved here, along with default values
function addedPlayerData() { return {
- weather: "Yes",
- happiness: new Decimal(72),
}}
// Display extra things at the top of the page
var displayThings = [
- function() {if (player.points.eq(69)) return "Tee hee!"},
- function() {if (player.f.points.gt(1)) return `You have ${player.f.points} farm points. (Which do nothing.)`},
- function() {if (inChallenge("c", 11)) return "The game is currently