Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions awesome_clicker/static/src/click_rewards.js
Original file line number Diff line number Diff line change
@@ -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,
},
];
6 changes: 6 additions & 0 deletions awesome_clicker/static/src/clicker_hook.js
Original file line number Diff line number Diff line change
@@ -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"));
}
36 changes: 36 additions & 0 deletions awesome_clicker/static/src/clicker_migration.js
Original file line number Diff line number Diff line change
@@ -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;
}
155 changes: 155 additions & 0 deletions awesome_clicker/static/src/clicker_model.js
Original file line number Diff line number Diff line change
@@ -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;
}
}
27 changes: 27 additions & 0 deletions awesome_clicker/static/src/clicker_provider.js
Original file line number Diff line number Diff line change
@@ -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",
});
},
},
];
},
});
54 changes: 54 additions & 0 deletions awesome_clicker/static/src/clicker_service.js
Original file line number Diff line number Diff line change
@@ -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);
Original file line number Diff line number Diff line change
@@ -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 });
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
<t t-name="awesome_clicker.ClickerSystray">
<div class="o_nav_entry">
<Dropdown>
<button>
<ClickerValue/> <i class="fa fa-mouse-pointer fa-fw"></i>,
<t t-esc="this.numberTrees"/> <i class="fa fa-tree fa-fw"></i>,
<t t-esc="this.numberFruits"/> <i class="fa fa-apple fa-fw"></i>
</button>
<t t-set-slot="content">
<DropdownItem>
<button class="btn btn-secondary" t-on-click="this.openClientAction"> Open the clicker game </button>
</DropdownItem>
<DropdownItem>
<button class="btn btn-secondary" t-on-click="() => this.clicker.buyBot('clickbot')"> Buy a ClickBot </button>
</DropdownItem>
<DropdownItem t-foreach="this.clicker.trees" t-as="tree" t-key="tree">
<t t-esc="tree_value.purchased"/>x
<t t-esc="tree"/>
</DropdownItem>
<DropdownItem t-foreach="this.clicker.fruits" t-as="fruit" t-key="fruit">
<t t-esc="fruit_value"/>x
<t t-esc="fruit"/>
</DropdownItem>
</t>
</Dropdown>
</div>
</t>
</templates>
Loading