Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
34c6572
[ADD] awesome_clicker: add clicker in systray
robal-odoo Dec 24, 2025
2004e93
[IMP] awesome_clicker: count external clicks
robal-odoo Dec 24, 2025
6eaaa5d
[IMP] awesome_clicker: add clicker action
robal-odoo Dec 24, 2025
7c08dc6
[IMP] awesome_clicker: move the clicker state to a service
robal-odoo Dec 24, 2025
1e71deb
[IMP] awesome_clicker: add a "useClicker" hook
robal-odoo Dec 24, 2025
d89dddd
[IMP] awesome_clicker: humanize display of click numbers
robal-odoo Dec 24, 2025
cefa856
[IMP] awesome_clicker: add tooltip to ClickValue component
robal-odoo Dec 24, 2025
675acf0
[IMP] awesome_clicker: add clickbots
robal-odoo Dec 26, 2025
c695123
[REF] awesome_clicker: refactor clicker state to a class model
robal-odoo Dec 26, 2025
2b99d9e
[IMP] awesome_clicker: add milestone effect
robal-odoo Dec 26, 2025
46353e9
[IMP] awesome_clicker: add bigbots
robal-odoo Dec 26, 2025
031b8d7
[IMP] awesome_clicker: add power level increases
robal-odoo Dec 26, 2025
e978e41
[REF] awesome_clicker: big refactoring
robal-odoo Dec 26, 2025
1acb47a
[IMP] awesome_clicker: add random rewards
robal-odoo Dec 26, 2025
950bac5
[IMP] awesome_clicker: add random reward on patched form controller
robal-odoo Dec 26, 2025
9c39ea7
[IMP] awesome_clicker: add commands
robal-odoo Dec 26, 2025
ee0cb49
[IMP] awesome_clicker: add fruits and trees
robal-odoo Dec 26, 2025
a9c4fdf
[IMP] awesome_clicker: convert systray to dropdown
robal-odoo Dec 26, 2025
f41c16e
[IMP] awesome_clicker: display shop/action using notebook components
robal-odoo Dec 26, 2025
b289cb7
[IMP] awesome_clicker: add state persistence
robal-odoo Dec 26, 2025
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
25 changes: 25 additions & 0 deletions awesome_clicker/static/src/click_value.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { Component, xml } from "@odoo/owl";
import { humanNumber } from "@web/core/utils/numbers";

export class ClickValue extends Component {
static props = {
label: { type: String, optional: true },
icon: { type: String, optional: true },
value: Number,
};
static template = xml`
<span t-att-data-tooltip="props.value">
<t t-esc="props.label || ''"/>
<t t-esc="format(props.value)"/>
<i t-if="props.icon" class="fa fa-fw" t-att-class="props.icon"/>
</span>
`;

format(value) {
if (value < 1000) {
return value;
} else {
return humanNumber(value, { minDigits: 0, decimals: 1 });
}
}
}
16 changes: 16 additions & 0 deletions awesome_clicker/static/src/clicker_action/clicker_action.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { Component } from "@odoo/owl";
import { registry } from "@web/core/registry";
import { useClicker } from "../utils";
import { ClickValue } from "../click_value";
import { Notebook } from "@web/core/notebook/notebook";

export class ClickerAction extends Component {
static template = "awesome_clicker.clicker_action";
static components = { ClickValue, Notebook };

setup() {
this.clicker = useClicker();
}
}

registry.category("actions").add("awesome_clicker.clicker_action", ClickerAction);
52 changes: 52 additions & 0 deletions awesome_clicker/static/src/clicker_action/clicker_action.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<?xml version="1.0" encoding="UTF-8" ?>
<templates xml:space="preserve">

<t t-name="awesome_clicker.clicker_action">
<div class="ms-1 mt-1">
<ClickValue label="'Clicks: '" value="clicker.clicks"/>
<!-- 9 -->
<button type="button" class="btn btn-primary" t-on-click="() => clicker.increment(999999)">Increment</button>
<t t-foreach="Object.keys(clicker.fruits)" t-as="fruitType" t-key="fruitType">
<br/>
<ClickValue label="`${fruitType}: `" value="clicker.fruits[fruitType]"/>
</t>
</div>
<div t-if="clicker.level >= 1" class="ms-1 mt-1">
<Notebook>
<t t-set-slot="page_1" title="'Bots'" isVisible="clicker.level >= 1">
<t t-foreach="clicker.getItemsByCategory('Bots')" t-as="item" t-key="item.id">
<div t-if="clicker.level &gt;= item.minLevel" class="card">
<div class="card-header">
<t t-esc="`${item.currentNumber(clicker)}x ${item.name} `"/>
<t t-if="item.clicks" t-esc="`(${item.clicks} clicks/${clicker.botFrequency/1000} seconds) `"/>
<i class="fa" t-att-class="item.icon"/>
</div>
<div class="card-body">
<button type="button" class="btn btn-primary" t-att-disabled="clicker.clicks &lt; item.price" t-on-click="() => item.buy(clicker)">
<t t-esc="`Buy ${item.name} (${item.price} clicks)`"/>
</button>
</div>
</div>
</t>
</t>
<t t-set-slot="page_2" title="'Trees'" isVisible="clicker.level >= 4">
<t t-foreach="clicker.getItemsByCategory('Trees')" t-as="item" t-key="item.id">
<div t-if="clicker.level &gt;= item.minLevel" class="card">
<div class="card-header">
<t t-esc="`${item.currentNumber(clicker)}x ${item.name} `"/>
<t t-esc="`(1 fruit/${clicker.treeFrequency/1000} seconds) `"/>
<i class="fa" t-att-class="item.icon"/>
</div>
<div class="card-body">
<button type="button" class="btn btn-primary" t-att-disabled="clicker.clicks &lt; item.price" t-on-click="() => item.buy(clicker)">
<t t-esc="`Buy ${item.name} (${item.price} clicks)`"/>
</button>
</div>
</div>
</t>
</t>
</Notebook>
</div>
</t>

</templates>
19 changes: 19 additions & 0 deletions awesome_clicker/static/src/clicker_commands.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { registry } from "@web/core/registry";
import { doClickerAction } from "./clicker_service";

const commandRegistry = registry.category("command_provider");

commandRegistry.add("clicker", {
provide: (env, options) => [
{
name: "Open Clicker Game",
action: () => doClickerAction(env.services.action),
},
{
name: "Buy 1 click bot",
action: () => {
env.services["awesome_clicker.game_service"].purchase("clickbot");
},
},
],
});
152 changes: 152 additions & 0 deletions awesome_clicker/static/src/clicker_data.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
export const CLICKBOT_CLICKS = 10;
export const BIGBOT_CLICKS = 100;
export const BOT_FREQUENCY = 10000; // In milliseconds
export const TREE_FREQUENCY = 30000; // In milliseconds

export const PURCHASABLE_REWARDS = [
{
id: "clickbot",
name: "ClickBot",
icon: "fa-android",
category: "Bots",
clicks: CLICKBOT_CLICKS,
price: 1000,
minLevel: 1,
currentNumber: (clicker) => clicker.clickBots,
buy(clicker) {
if (clicker.verifyPurchase(1, 1000)) {
clicker.clickBots++;
}
},
},
{
id: "bigbot",
name: "BigBot",
icon: "fa-android",
category: "Bots",
clicks: BIGBOT_CLICKS,
price: 5000,
minLevel: 2,
currentNumber: (clicker) => clicker.bigBots,
buy(clicker) {
if (clicker.verifyPurchase(2, 5000)) {
clicker.bigBots++;
}
},
},
{
id: "power-multiplier",
name: "Power Multiplier",
icon: "fa-bolt",
category: "Bots",
price: 50000,
minLevel: 3,
currentNumber: (clicker) => clicker.multiplier,
buy(clicker) {
if (clicker.verifyPurchase(3, 50000)) {
clicker.multiplier++;
}
},
},
{
id: "pear-tree",
name: "Pear Tree",
icon: "fa-tree",
category: "Trees",
price: 1000000,
minLevel: 4,
currentNumber: (clicker) => clicker.trees.pear ?? 0,
buy(clicker) {
if (clicker.verifyPurchase(4, 1000000)) {
if (!clicker.trees.pear) {
clicker.trees.pear = 0;
}
clicker.trees.pear++;
}
},
},
{
id: "cherry-tree",
name: "Cherry Tree",
icon: "fa-tree",
category: "Trees",
price: 1000000,
minLevel: 4,
currentNumber: (clicker) => clicker.trees.cherry ?? 0,
buy(clicker) {
if (clicker.verifyPurchase(4, 1000000)) {
if (!clicker.trees.cherry) {
clicker.trees.cherry = 0;
}
clicker.trees.cherry++;
}
},
},
];

// Ordered milestones
export const MILESTONES = [
{
level: 1,
clicks: 1000,
event: "MILESTONE_1k",
description: "You can now buy clickbots.",
},
{
level: 2,
clicks: 5000,
event: "MILESTONE_5k",
description: "You can now buy bigbots.",
},
{
level: 3,
clicks: 100000,
event: "MILESTONE_100k",
description: "You can now increase your bots' power.",
},
{
level: 4,
clicks: 1000000,
event: "MILESTONE_1m",
description: "You can now plant trees.",
},
];

export const RANDOM_REWARDS = [
{
description: "Get 1 click bot",
apply(clicker) {
clicker.clickBots += 1;
},
maxLevel: 1,
},
{
description: "Get 3 click bots",
apply(clicker) {
clicker.clickBots += 3;
},
minLevel: 1,
maxLevel: 2,
},
{
description: "Get 3 big bots",
apply(clicker) {
clicker.bigBots += 3;
},
minLevel: 2,
},
{
description: "Get 10 big bots",
apply(clicker) {
clicker.bigBots += 10;
},
minLevel: 3,
},
{
description: "Increase bot power!",
apply(clicker) {
clicker.multipler += 1;
},
minLevel: 3,
},
];
28 changes: 28 additions & 0 deletions awesome_clicker/static/src/clicker_menu/clicker_menu.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { Component } from "@odoo/owl";
import { registry } from "@web/core/registry";
import { useService } from "@web/core/utils/hooks";
import { useClicker } from "../utils";
import { ClickValue } from "../click_value";
import { doClickerAction } from "../clicker_service";
import { Dropdown } from "@web/core/dropdown/dropdown";
import { DropdownItem } from "@web/core/dropdown/dropdown_item";

export class ClickerMenu extends Component {
static template = "awesome_clicker.clicker_menu";
static components = { ClickValue, Dropdown, DropdownItem };

setup() {
this.clicker = useClicker();
this.action = useService("action");
}

doAction() {
doClickerAction(this.action);
}
}

export const systrayItem = {
Component: ClickerMenu,
};

registry.category("systray").add("awesome_clicker.clicker_menu", systrayItem);
34 changes: 34 additions & 0 deletions awesome_clicker/static/src/clicker_menu/clicker_menu.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?xml version="1.0" encoding="UTF-8" ?>
<templates xml:space="preserve">

<t t-name="awesome_clicker.clicker_menu">
<Dropdown>
<button type="button">
<div class="o_nav_entry">
<ClickValue icon="'fa-mouse-pointer'" value="clicker.clicks"/>
</div>
<div class="o_nav_entry">
<ClickValue icon="'fa-tree'" value="clicker.totalTrees"/>
</div>
<div class="o_nav_entry">
<ClickValue icon="'fa-apple'" value="clicker.totalFruits"/>
</div>
</button>
<t t-set-slot="content">
<DropdownItem>
<button type="button" class="btn btn-secondary" t-on-click="doAction">Open the clicker game</button>
</DropdownItem>
<DropdownItem>
<button type="button" class="btn btn-secondary" t-if="clicker.level >= 1" t-on-click="() => clicker.purchase('clickbot')">Buy a ClickBot</button>
</DropdownItem>
<DropdownItem t-foreach="Object.keys(clicker.trees)" t-as="treeType" t-key="treeType">
<t t-esc="`${clicker.trees[treeType]}x ${treeType} tree`"/>
</DropdownItem>
<DropdownItem t-foreach="Object.keys(clicker.fruits)" t-as="fruitType" t-key="fruitType">
<t t-esc="`${clicker.fruits[fruitType]}x ${fruitType}`"/>
</DropdownItem>
</t>
</Dropdown>
</t>

</templates>
Loading