diff --git a/awesome_clicker/static/src/click_rewards.js b/awesome_clicker/static/src/click_rewards.js new file mode 100644 index 00000000000..b1b60248f36 --- /dev/null +++ b/awesome_clicker/static/src/click_rewards.js @@ -0,0 +1,24 @@ +export const rewards = [ + { + description: "Get 1 free click", + apply(clicker) { + clicker.increment(1); + }, + maxLevel: 3, + }, + { + description: "Get 10 free click", + apply(clicker) { + clicker.increment(10); + }, + minLevel: 3, + maxLevel: 4, + }, + { + description: "Free power upgrade", + apply(clicker) { + clicker.power++; + }, + minLevel: 3, + }, +]; diff --git a/awesome_clicker/static/src/clicker_hook.js b/awesome_clicker/static/src/clicker_hook.js new file mode 100644 index 00000000000..6e46efd3468 --- /dev/null +++ b/awesome_clicker/static/src/clicker_hook.js @@ -0,0 +1,6 @@ +import { useState } from "@odoo/owl"; +import { useService } from "@web/core/utils/hooks"; + +export function useClicker() { + return useState(useService("awesome_clicker.clicker")); +} diff --git a/awesome_clicker/static/src/clicker_migration.js b/awesome_clicker/static/src/clicker_migration.js new file mode 100644 index 00000000000..750543aff3e --- /dev/null +++ b/awesome_clicker/static/src/clicker_migration.js @@ -0,0 +1,36 @@ +export const CURRENT_VERSION = 2.0; +export const migrations = [ + { + fromVersion: 1.0, + toVersion: 2.0, + apply: (state) => { + state.trees.peachTree = { + price: 1500000, + level: 5, + produce: 'peach', + purchased: 0, + }; + state.fruits.peach = 0; + }, + }, +]; + +export function migrate(localState) { + console.log("Migration started") + if (localState?.version < CURRENT_VERSION) { + console.log("Current save version: " + localState.version); + for (const migration of migrations) { + console.log("Checking migration from " + migration.fromVersion + " to " + migration.toVersion); + if (localState.version === migration.fromVersion){ + console.log("Applying migration"); + migration.apply(localState); + console.log("Updating save version"); + localState.version = migration.toVersion; + console.log("Update completed"); + } + } + } + console.log("LocalState updated"); + console.log(localState); + return localState; +} diff --git a/awesome_clicker/static/src/clicker_model.js b/awesome_clicker/static/src/clicker_model.js new file mode 100644 index 00000000000..b079a87d2c9 --- /dev/null +++ b/awesome_clicker/static/src/clicker_model.js @@ -0,0 +1,155 @@ +import { Reactive } from "@web/core/utils/reactive"; +import { EventBus } from "@odoo/owl"; +import { rewards } from "./click_rewards"; +import { choose } from "./utils"; +import { CURRENT_VERSION } from "./clicker_migration"; + +export class ClickerModel extends Reactive { + constructor() { + super(); + this.version = CURRENT_VERSION; + this.clicks = 0; + this.level = 0; + this.bus = new EventBus(); + this.power = 1; + this.bots = { + clickbot: { + price: 1000, + level: 1, + increment: 10, + purchased: 0, + }, + bigbot: { + price: 5000, + level: 2, + increment: 100, + purchased: 0, + }, + }; + this.trees = { + pearTree:{ + price: 1000000, + level: 4, + produce: 'pear', + purchased: 0, + }, + cherryTree:{ + price: 1000000, + level: 4, + produce: 'cherry', + purchased: 0, + }, + peachTree: { + price: 1500000, + level: 5, + produce: 'peach', + purchased: 0, + }, + }; + this.fruits = { + pear: 0, + cherry: 0, + peach: 0, + }; + + document.addEventListener("click", () => this.increment(1), true); + + setInterval(() => { + for (const bot in this.bots){ + this.increment(this.bots[bot].increment * this.bots[bot].purchased * this.power); + } + }, 1000); + + setInterval(() => { + for (const tree in this.trees) { + this.fruits[this.trees[tree].produce] += this.trees[tree].purchased; + } + }, 30000); + } + + increment(inc) { + this.clicks += inc; + if( + this.milestones[this.level] && + this.clicks >= this.milestones[this.level].clicks + ) { + this.bus.trigger("MILESTONE", this.milestones[this.level]); + this.level += 1; + console.log("new level: " + this.level) + } + } + + buyBot(name) { + if (!Object.keys(this.bots).includes(name)){ + throw new Error('Invalid bot name ${name}'); + } + if (this.clicks < this.bots[name].price){ + return false; + } + + this.clicks -= this.bots[name].price; + this.bots[name].purchased++; + } + + getClicksPerSecond(){ + const totalCPS = Object.values(this.bots).reduce((total, bot) => { + return total + (bot.increment * bot.purchased * this.power); + }, 0); + this.clicksPerSecond = totalCPS; + return totalCPS; + } + + buyPower(){ + const powerPrice = 50000; + if (this.clicks < powerPrice) { + return false; + } + this.clicks -= powerPrice; + this.power += 1; + } + + buyTree(name) { + if (!Object.keys(this.trees).includes(name)){ + throw new Error(`Invalid tree name "${name}"`); + } + if (this.clicks < this.trees[name].price){ + return false; + } + this.clicks -= this.trees[name].price; + this.trees[name].purchased += 1; + } + + toJSON(){ + const json = Object.assign({}, this); + delete json["bus"]; + return json; + } + + static fromJSON(json){ + const clicker = new ClickerModel(); + const clickerInstance = Object.assign(clicker, json); + return clickerInstance; + } + + get milestones() { + return [ + { clicks: 1000, unlock: "clickBot" }, + { clicks: 5000, unlock: "bigBot" }, + { clicks: 100000, unlock: "power" }, + { clicks: 1000000, unlock: "trees" }, + { clicks: 1500000, unlock: "peach trees" } + ]; + } + + getReward(){ + const availableRewards = rewards.filter(reward => { + const minLevel = reward.minLevel ?? 0; + const maxLevel = reward.maxLevel ?? Infinity; + + return this.level >= minLevel && this.level <= maxLevel; + }); + const reward = choose(availableRewards); + this.bus.trigger("REWARD", reward); + return reward; + } +} diff --git a/awesome_clicker/static/src/clicker_provider.js b/awesome_clicker/static/src/clicker_provider.js new file mode 100644 index 00000000000..a59cc673b31 --- /dev/null +++ b/awesome_clicker/static/src/clicker_provider.js @@ -0,0 +1,27 @@ +import { registry } from "@web/core/registry"; + +const commandProviderRegistry = registry.category("command_provider"); + +commandProviderRegistry.add("clicker", { + provide: (env, options) => { + return [ + { + name: "Buy 1 click bot", + action() { + env.services["awesome_clicker.clicker"].buyBot("clickbot"); + }, + }, + { + name: "Open Clicker Game", + action() { + env.services.action.doAction({ + type: "ir.actions.client", + tag: "awesome_clicker.client_action", + target: "new", + name: "Clicker Game", + }); + }, + }, + ]; + }, +}); diff --git a/awesome_clicker/static/src/clicker_service.js b/awesome_clicker/static/src/clicker_service.js new file mode 100644 index 00000000000..bcc1047627b --- /dev/null +++ b/awesome_clicker/static/src/clicker_service.js @@ -0,0 +1,54 @@ +import { registry } from "@web/core/registry"; +import { ClickerModel } from "./clicker_model"; +import { browser } from "@web/core/browser/browser"; +import { migrate } from "./clicker_migration"; + +const clickerService = { + dependencies: ["action", "effect", "notification"], + start(env, services) { + const localState = migrate(JSON.parse(browser.localStorage.getItem("clickerState"))); + const clickerModel = localState ? ClickerModel.fromJSON(localState) : new ClickerModel(); + const bus = clickerModel.bus; + + setInterval(() => { + browser.localStorage.setItem("clickerState", JSON.stringify(clickerModel)); + }, 10000); + + bus.addEventListener("MILESTONE", (ev) => { + services.effect.add({ + message: `Milestone reached! You can now buy ${ev.detail.unlock}`, + type: "rainbow_man", + }); + }); + + bus.addEventListener("REWARD", (ev) => { + const reward = ev.detail; + const closeNotification = services.notification.add( + `Congrats you won a reward: "${reward.description}"`, + { + type: "success", + sticky: true, + buttons: [ + { + name: "Collect", + onClick: () => { + reward.apply(clickerModel); + closeNotification(); + services.action.doAction({ + type: "ir.actions.client", + tag: "awesome_clicker.client_action", + target: "new", + name: "Clicker Game", + }); + }, + }, + ], + }, + ); + }); + + return clickerModel; + }, +}; + +registry.category("services").add("awesome_clicker.clicker", clickerService); diff --git a/awesome_clicker/static/src/clicker_systray_item/clicker_systray_item.js b/awesome_clicker/static/src/clicker_systray_item/clicker_systray_item.js new file mode 100644 index 00000000000..a3c27d50855 --- /dev/null +++ b/awesome_clicker/static/src/clicker_systray_item/clicker_systray_item.js @@ -0,0 +1,49 @@ +import { Component } from "@odoo/owl"; +import { registry } from "@web/core/registry"; +import { useService } from "@web/core/utils/hooks"; +import { useClicker } from "../clicker_hook"; +import { ClickerValue } from "../clicker_value/clicker_value"; +import { Dropdown } from "@web/core/dropdown/dropdown"; +import { DropdownItem } from "@web/core/dropdown/dropdown_item"; + +class ClickerSystray extends Component { + static template = "awesome_clicker.ClickerSystray"; + static props = {}; + static components = { ClickerValue, Dropdown, DropdownItem }; + + setup() { + this.clicker = useClicker() + this.action = useService("action"); + } + + openClientAction() { + this.action.doAction({ + type: "ir.actions.client", + tag: "awesome_clicker.client_action", + target: "new", + name: "Clicker Game", + }); + } + + get numberTrees(){ + let sum = 0; + for (const tree in this.clicker.trees){ + sum += this.clicker.trees[tree].purchased; + } + return sum; + } + + get numberFruits(){ + let sum = 0; + for (const fruit in this.clicker.fruits){ + sum += this.clicker.fruits[fruit]; + } + return sum; + } +} + +export const systrayItem = { + Component: ClickerSystray, +}; + +registry.category("systray").add("awesome_clicker.ClickerSystray", systrayItem, { sequence: 1000 }); diff --git a/awesome_clicker/static/src/clicker_systray_item/clicker_systray_item.xml b/awesome_clicker/static/src/clicker_systray_item/clicker_systray_item.xml new file mode 100644 index 00000000000..209ef2b6249 --- /dev/null +++ b/awesome_clicker/static/src/clicker_systray_item/clicker_systray_item.xml @@ -0,0 +1,30 @@ + + + +
+ + + + + + + + + + + x + + + + x + + + + +
+
+
diff --git a/awesome_clicker/static/src/clicker_value/clicker_value.js b/awesome_clicker/static/src/clicker_value/clicker_value.js new file mode 100644 index 00000000000..a5018bad8ac --- /dev/null +++ b/awesome_clicker/static/src/clicker_value/clicker_value.js @@ -0,0 +1,18 @@ +import { Component } from "@odoo/owl"; +import { humanNumber } from "@web/core/utils/numbers"; +import { useClicker } from "../clicker_hook"; + +export class ClickerValue extends Component { + static template = "awesome_clicker.ClickerValue"; + static props = {}; + + setup() { + this.clicker = useClicker(); + } + + get humanizedClicks() { + return humanNumber(this.clicker.clicks, { + decimals: 1, + }); + } +} diff --git a/awesome_clicker/static/src/clicker_value/clicker_value.xml b/awesome_clicker/static/src/clicker_value/clicker_value.xml new file mode 100644 index 00000000000..24fd29643f2 --- /dev/null +++ b/awesome_clicker/static/src/clicker_value/clicker_value.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/awesome_clicker/static/src/client_action/client_action.js b/awesome_clicker/static/src/client_action/client_action.js new file mode 100644 index 00000000000..c1f13da18ac --- /dev/null +++ b/awesome_clicker/static/src/client_action/client_action.js @@ -0,0 +1,17 @@ +import { Component } from "@odoo/owl"; +import { registry } from "@web/core/registry"; +import { useClicker } from "../clicker_hook"; +import { ClickerValue } from "../clicker_value/clicker_value"; +import { Notebook } from "@web/core/notebook/notebook"; + +class ClickerClientAction extends Component { + static template = "awesome_clicker.ClientAction"; + static props = ["*"]; + static components = { ClickerValue, Notebook }; + + setup() { + this.clicker = useClicker() + } +} + +registry.category("actions").add("awesome_clicker.client_action", ClickerClientAction); diff --git a/awesome_clicker/static/src/client_action/client_action.xml b/awesome_clicker/static/src/client_action/client_action.xml new file mode 100644 index 00000000000..75aac5a537d --- /dev/null +++ b/awesome_clicker/static/src/client_action/client_action.xml @@ -0,0 +1,97 @@ + + + +
+
+
+ Current Clicks: + +
+
+ +
+
+ Current Level: +
+
+
+ + + +
+

Bots ( clicks/second)

+
+ +
+
+ x (+ clicks/seconds) + +
+
+ Total clicks/seconds: +
+
+ +
+
+
+
+
+ +
+

Power Upgrades

+
+
+
+ x + +
+
+ +
+
+
+
+
+ + +
+

Trees

+
+ +
+
+ x (1x /30seconds) + +
+
+ +
+
+
+
+ +

Fruits

+
+ +
+
+ x +
+
+
+
+
+
+
+
+ +
diff --git a/awesome_clicker/static/src/form_controller/form_controller_patch.js b/awesome_clicker/static/src/form_controller/form_controller_patch.js new file mode 100644 index 00000000000..1ff97dc398c --- /dev/null +++ b/awesome_clicker/static/src/form_controller/form_controller_patch.js @@ -0,0 +1,18 @@ +import { FormController } from "@web/views/form/form_controller"; +import { patch } from "@web/core/utils/patch"; +import { useClicker } from "../clicker_hook"; + +const FormControllerPatch = { + setup() { + super.setup(...arguments); + const clicker = useClicker(); + console.log("Reward ?") + const random = Math.random; + if (Math.random() < 0.05){ + console.log("Reward granted"); + clicker.getReward(); + } + }, +}; + +patch(FormController.prototype, FormControllerPatch); diff --git a/awesome_clicker/static/src/utils.js b/awesome_clicker/static/src/utils.js new file mode 100644 index 00000000000..f3f7c3977e9 --- /dev/null +++ b/awesome_clicker/static/src/utils.js @@ -0,0 +1,3 @@ +export function choose(list) { + return list[Math.floor(Math.random() * list.length)]; +}