pet-terminal 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +268 -0
- package/dist/commands/care.d.ts +3 -0
- package/dist/commands/care.d.ts.map +1 -0
- package/dist/commands/care.js +103 -0
- package/dist/commands/care.js.map +1 -0
- package/dist/commands/clean.d.ts +3 -0
- package/dist/commands/clean.d.ts.map +1 -0
- package/dist/commands/clean.js +105 -0
- package/dist/commands/clean.js.map +1 -0
- package/dist/commands/feed.d.ts +3 -0
- package/dist/commands/feed.d.ts.map +1 -0
- package/dist/commands/feed.js +106 -0
- package/dist/commands/feed.js.map +1 -0
- package/dist/commands/git.d.ts +3 -0
- package/dist/commands/git.d.ts.map +1 -0
- package/dist/commands/git.js +138 -0
- package/dist/commands/git.js.map +1 -0
- package/dist/commands/heal.d.ts +3 -0
- package/dist/commands/heal.d.ts.map +1 -0
- package/dist/commands/heal.js +106 -0
- package/dist/commands/heal.js.map +1 -0
- package/dist/commands/init.d.ts +3 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +89 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/inventory.d.ts +3 -0
- package/dist/commands/inventory.d.ts.map +1 -0
- package/dist/commands/inventory.js +113 -0
- package/dist/commands/inventory.js.map +1 -0
- package/dist/commands/play.d.ts +3 -0
- package/dist/commands/play.d.ts.map +1 -0
- package/dist/commands/play.js +110 -0
- package/dist/commands/play.js.map +1 -0
- package/dist/commands/shop.d.ts +3 -0
- package/dist/commands/shop.d.ts.map +1 -0
- package/dist/commands/shop.js +197 -0
- package/dist/commands/shop.js.map +1 -0
- package/dist/commands/sleep.d.ts +3 -0
- package/dist/commands/sleep.d.ts.map +1 -0
- package/dist/commands/sleep.js +48 -0
- package/dist/commands/sleep.js.map +1 -0
- package/dist/commands/status.d.ts +3 -0
- package/dist/commands/status.d.ts.map +1 -0
- package/dist/commands/status.js +98 -0
- package/dist/commands/status.js.map +1 -0
- package/dist/commands/sync.d.ts +3 -0
- package/dist/commands/sync.d.ts.map +1 -0
- package/dist/commands/sync.js +66 -0
- package/dist/commands/sync.js.map +1 -0
- package/dist/commands/tutorial.d.ts +3 -0
- package/dist/commands/tutorial.d.ts.map +1 -0
- package/dist/commands/tutorial.js +210 -0
- package/dist/commands/tutorial.js.map +1 -0
- package/dist/core/auto-care.d.ts +63 -0
- package/dist/core/auto-care.d.ts.map +1 -0
- package/dist/core/auto-care.js +295 -0
- package/dist/core/auto-care.js.map +1 -0
- package/dist/core/config.d.ts +89 -0
- package/dist/core/config.d.ts.map +1 -0
- package/dist/core/config.js +195 -0
- package/dist/core/config.js.map +1 -0
- package/dist/core/database.d.ts +29 -0
- package/dist/core/database.d.ts.map +1 -0
- package/dist/core/database.js +81 -0
- package/dist/core/database.js.map +1 -0
- package/dist/core/inventory.d.ts +63 -0
- package/dist/core/inventory.d.ts.map +1 -0
- package/dist/core/inventory.js +140 -0
- package/dist/core/inventory.js.map +1 -0
- package/dist/core/level-system.d.ts +32 -0
- package/dist/core/level-system.d.ts.map +1 -0
- package/dist/core/level-system.js +76 -0
- package/dist/core/level-system.js.map +1 -0
- package/dist/core/pet.d.ts +123 -0
- package/dist/core/pet.d.ts.map +1 -0
- package/dist/core/pet.js +781 -0
- package/dist/core/pet.js.map +1 -0
- package/dist/core/shop.d.ts +93 -0
- package/dist/core/shop.d.ts.map +1 -0
- package/dist/core/shop.js +205 -0
- package/dist/core/shop.js.map +1 -0
- package/dist/core/time-decay.d.ts +30 -0
- package/dist/core/time-decay.d.ts.map +1 -0
- package/dist/core/time-decay.js +149 -0
- package/dist/core/time-decay.js.map +1 -0
- package/dist/core/welcome.d.ts +31 -0
- package/dist/core/welcome.d.ts.map +1 -0
- package/dist/core/welcome.js +180 -0
- package/dist/core/welcome.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +80 -0
- package/dist/index.js.map +1 -0
- package/dist/monitor/git-monitor.d.ts +90 -0
- package/dist/monitor/git-monitor.d.ts.map +1 -0
- package/dist/monitor/git-monitor.js +342 -0
- package/dist/monitor/git-monitor.js.map +1 -0
- package/dist/types/auto-care.d.ts +86 -0
- package/dist/types/auto-care.d.ts.map +1 -0
- package/dist/types/auto-care.js +15 -0
- package/dist/types/auto-care.js.map +1 -0
- package/dist/types/config.d.ts +42 -0
- package/dist/types/config.d.ts.map +1 -0
- package/dist/types/config.js +24 -0
- package/dist/types/config.js.map +1 -0
- package/dist/types/git.d.ts +68 -0
- package/dist/types/git.d.ts.map +1 -0
- package/dist/types/git.js +14 -0
- package/dist/types/git.js.map +1 -0
- package/dist/types/items.d.ts +41 -0
- package/dist/types/items.d.ts.map +1 -0
- package/dist/types/items.js +286 -0
- package/dist/types/items.js.map +1 -0
- package/dist/types/pet.d.ts +42 -0
- package/dist/types/pet.d.ts.map +1 -0
- package/dist/types/pet.js +16 -0
- package/dist/types/pet.js.map +1 -0
- package/dist/types/species.d.ts +30 -0
- package/dist/types/species.d.ts.map +1 -0
- package/dist/types/species.js +164 -0
- package/dist/types/species.js.map +1 -0
- package/dist/types/stats.d.ts +22 -0
- package/dist/types/stats.d.ts.map +1 -0
- package/dist/types/stats.js +18 -0
- package/dist/types/stats.js.map +1 -0
- package/dist/types/time.d.ts +30 -0
- package/dist/types/time.d.ts.map +1 -0
- package/dist/types/time.js +79 -0
- package/dist/types/time.js.map +1 -0
- package/dist/ui/ascii-art.d.ts +4 -0
- package/dist/ui/ascii-art.d.ts.map +1 -0
- package/dist/ui/ascii-art.js +170 -0
- package/dist/ui/ascii-art.js.map +1 -0
- package/dist/ui/display.d.ts +11 -0
- package/dist/ui/display.d.ts.map +1 -0
- package/dist/ui/display.js +93 -0
- package/dist/ui/display.js.map +1 -0
- package/dist/ui/help.d.ts +17 -0
- package/dist/ui/help.d.ts.map +1 -0
- package/dist/ui/help.js +280 -0
- package/dist/ui/help.js.map +1 -0
- package/dist/ui/progress-bar.d.ts +9 -0
- package/dist/ui/progress-bar.d.ts.map +1 -0
- package/dist/ui/progress-bar.js +57 -0
- package/dist/ui/progress-bar.js.map +1 -0
- package/package.json +61 -0
package/dist/core/pet.js
ADDED
|
@@ -0,0 +1,781 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Pet = void 0;
|
|
4
|
+
const pet_1 = require("../types/pet");
|
|
5
|
+
const species_1 = require("../types/species");
|
|
6
|
+
const stats_1 = require("../types/stats");
|
|
7
|
+
const level_system_1 = require("./level-system");
|
|
8
|
+
const time_decay_1 = require("./time-decay");
|
|
9
|
+
const inventory_1 = require("./inventory");
|
|
10
|
+
const items_1 = require("../types/items");
|
|
11
|
+
const shop_1 = require("./shop");
|
|
12
|
+
const git_monitor_1 = require("../monitor/git-monitor");
|
|
13
|
+
const git_1 = require("../types/git");
|
|
14
|
+
const auto_care_1 = require("./auto-care");
|
|
15
|
+
class Pet {
|
|
16
|
+
constructor(data, db) {
|
|
17
|
+
this.synced = false;
|
|
18
|
+
this.data = data;
|
|
19
|
+
this.db = db;
|
|
20
|
+
this.timeDecay = new time_decay_1.TimeDecay();
|
|
21
|
+
this.inventory = new inventory_1.Inventory(data.inventory || []);
|
|
22
|
+
}
|
|
23
|
+
static async loadOrCreate(db) {
|
|
24
|
+
if (db.hasPet()) {
|
|
25
|
+
const petData = db.getPet();
|
|
26
|
+
const pet = new Pet(petData, db);
|
|
27
|
+
// Auto-sync time on load
|
|
28
|
+
pet.syncTime();
|
|
29
|
+
return pet;
|
|
30
|
+
}
|
|
31
|
+
// Create a default pet
|
|
32
|
+
const now = new Date().toISOString();
|
|
33
|
+
const startingInventory = (0, items_1.getStartingInventory)();
|
|
34
|
+
const defaultData = {
|
|
35
|
+
id: (0, pet_1.generatePetId)(),
|
|
36
|
+
name: 'Buddy',
|
|
37
|
+
species: species_1.PetSpecies.CAT,
|
|
38
|
+
level: 1,
|
|
39
|
+
experience: 0,
|
|
40
|
+
stats: { ...pet_1.DEFAULT_STATS },
|
|
41
|
+
mood: species_1.MoodState.HAPPY,
|
|
42
|
+
isSleeping: false,
|
|
43
|
+
birthDate: now,
|
|
44
|
+
lastInteraction: now,
|
|
45
|
+
lastSaveTime: now,
|
|
46
|
+
lastUpdated: now,
|
|
47
|
+
totalInteractions: 0,
|
|
48
|
+
inventory: startingInventory,
|
|
49
|
+
coins: pet_1.STARTING_COINS,
|
|
50
|
+
coinHistory: [],
|
|
51
|
+
// Git tracking fields
|
|
52
|
+
lastGitCommit: undefined,
|
|
53
|
+
gitCommitCount: 0,
|
|
54
|
+
gitStreak: 0,
|
|
55
|
+
lastGitDate: undefined,
|
|
56
|
+
};
|
|
57
|
+
db.savePet(defaultData);
|
|
58
|
+
return new Pet(defaultData, db);
|
|
59
|
+
}
|
|
60
|
+
static createNew(db, name, species) {
|
|
61
|
+
const now = new Date().toISOString();
|
|
62
|
+
const startingInventory = (0, items_1.getStartingInventory)();
|
|
63
|
+
const newPet = {
|
|
64
|
+
id: (0, pet_1.generatePetId)(),
|
|
65
|
+
name,
|
|
66
|
+
species,
|
|
67
|
+
level: 1,
|
|
68
|
+
experience: 0,
|
|
69
|
+
stats: { ...pet_1.DEFAULT_STATS },
|
|
70
|
+
mood: species_1.MoodState.HAPPY,
|
|
71
|
+
isSleeping: false,
|
|
72
|
+
birthDate: now,
|
|
73
|
+
lastInteraction: now,
|
|
74
|
+
lastSaveTime: now,
|
|
75
|
+
lastUpdated: now,
|
|
76
|
+
totalInteractions: 0,
|
|
77
|
+
inventory: startingInventory,
|
|
78
|
+
coins: pet_1.STARTING_COINS,
|
|
79
|
+
coinHistory: [],
|
|
80
|
+
// Git tracking fields
|
|
81
|
+
lastGitCommit: undefined,
|
|
82
|
+
gitCommitCount: 0,
|
|
83
|
+
gitStreak: 0,
|
|
84
|
+
lastGitDate: undefined,
|
|
85
|
+
};
|
|
86
|
+
db.savePet(newPet);
|
|
87
|
+
return newPet;
|
|
88
|
+
}
|
|
89
|
+
// ===== ACTIONS =====
|
|
90
|
+
feed() {
|
|
91
|
+
if (this.data.isSleeping) {
|
|
92
|
+
return {
|
|
93
|
+
success: false,
|
|
94
|
+
message: `${this.data.name} is sleeping! Wake them up first.`,
|
|
95
|
+
statChanges: [],
|
|
96
|
+
xpGained: 0,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
if (this.data.stats.hunger >= 95) {
|
|
100
|
+
return {
|
|
101
|
+
success: false,
|
|
102
|
+
message: `${this.data.name} is too full to eat more!`,
|
|
103
|
+
statChanges: [],
|
|
104
|
+
xpGained: 0,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
const changes = [
|
|
108
|
+
{ stat: 'hunger', delta: 25 },
|
|
109
|
+
{ stat: 'happiness', delta: 4 },
|
|
110
|
+
{ stat: 'health', delta: 2 },
|
|
111
|
+
{ stat: 'cleanliness', delta: -4 },
|
|
112
|
+
{ stat: 'energy', delta: -2 },
|
|
113
|
+
];
|
|
114
|
+
return this.performAction(changes, 10, 'fed');
|
|
115
|
+
}
|
|
116
|
+
play() {
|
|
117
|
+
if (this.data.isSleeping) {
|
|
118
|
+
return {
|
|
119
|
+
success: false,
|
|
120
|
+
message: `${this.data.name} is sleeping! Wake them up first.`,
|
|
121
|
+
statChanges: [],
|
|
122
|
+
xpGained: 0,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
if (this.data.stats.energy < 15) {
|
|
126
|
+
return {
|
|
127
|
+
success: false,
|
|
128
|
+
message: `${this.data.name} is too tired to play! Let them rest.`,
|
|
129
|
+
statChanges: [],
|
|
130
|
+
xpGained: 0,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
const changes = [
|
|
134
|
+
{ stat: 'hunger', delta: -5 },
|
|
135
|
+
{ stat: 'happiness', delta: 23 },
|
|
136
|
+
{ stat: 'health', delta: 1 },
|
|
137
|
+
{ stat: 'cleanliness', delta: -4 },
|
|
138
|
+
{ stat: 'energy', delta: -20 },
|
|
139
|
+
];
|
|
140
|
+
return this.performAction(changes, 15, 'played with');
|
|
141
|
+
}
|
|
142
|
+
clean() {
|
|
143
|
+
if (this.data.isSleeping) {
|
|
144
|
+
return {
|
|
145
|
+
success: false,
|
|
146
|
+
message: `${this.data.name} is sleeping! Wake them up first.`,
|
|
147
|
+
statChanges: [],
|
|
148
|
+
xpGained: 0,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
if (this.data.stats.cleanliness >= 95) {
|
|
152
|
+
return {
|
|
153
|
+
success: false,
|
|
154
|
+
message: `${this.data.name} is already very clean!`,
|
|
155
|
+
statChanges: [],
|
|
156
|
+
xpGained: 0,
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
const changes = [
|
|
160
|
+
{ stat: 'hunger', delta: -2 },
|
|
161
|
+
{ stat: 'happiness', delta: 5 },
|
|
162
|
+
{ stat: 'health', delta: 3 },
|
|
163
|
+
{ stat: 'cleanliness', delta: 35 },
|
|
164
|
+
{ stat: 'energy', delta: -3 },
|
|
165
|
+
];
|
|
166
|
+
return this.performAction(changes, 8, 'cleaned');
|
|
167
|
+
}
|
|
168
|
+
sleep() {
|
|
169
|
+
if (!this.data.isSleeping) {
|
|
170
|
+
// Going to sleep
|
|
171
|
+
this.data.isSleeping = true;
|
|
172
|
+
this.updateInteraction();
|
|
173
|
+
this.save();
|
|
174
|
+
return {
|
|
175
|
+
success: true,
|
|
176
|
+
message: `${this.data.name} is now sleeping. Shh! 💤`,
|
|
177
|
+
statChanges: [],
|
|
178
|
+
xpGained: 5,
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
else {
|
|
182
|
+
// Waking up
|
|
183
|
+
const changes = [
|
|
184
|
+
{ stat: 'hunger', delta: -5 },
|
|
185
|
+
{ stat: 'happiness', delta: 8 },
|
|
186
|
+
{ stat: 'health', delta: 5 },
|
|
187
|
+
{ stat: 'cleanliness', delta: -2 },
|
|
188
|
+
{ stat: 'energy', delta: 80 },
|
|
189
|
+
];
|
|
190
|
+
this.data.isSleeping = false;
|
|
191
|
+
const result = this.performAction(changes, 5, 'woke up');
|
|
192
|
+
result.message = `${this.data.name} woke up refreshed! ☀️`;
|
|
193
|
+
return result;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
heal() {
|
|
197
|
+
if (this.data.isSleeping) {
|
|
198
|
+
return {
|
|
199
|
+
success: false,
|
|
200
|
+
message: `${this.data.name} is sleeping! Wake them up first.`,
|
|
201
|
+
statChanges: [],
|
|
202
|
+
xpGained: 0,
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
if (this.data.stats.health >= 90) {
|
|
206
|
+
return {
|
|
207
|
+
success: false,
|
|
208
|
+
message: `${this.data.name} is already very healthy!`,
|
|
209
|
+
statChanges: [],
|
|
210
|
+
xpGained: 0,
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
const changes = [
|
|
214
|
+
{ stat: 'hunger', delta: -3 },
|
|
215
|
+
{ stat: 'happiness', delta: 2 },
|
|
216
|
+
{ stat: 'health', delta: 30 },
|
|
217
|
+
{ stat: 'cleanliness', delta: 5 },
|
|
218
|
+
{ stat: 'energy', delta: -5 },
|
|
219
|
+
];
|
|
220
|
+
return this.performAction(changes, 20, 'healed');
|
|
221
|
+
}
|
|
222
|
+
// ===== STATE QUERIES =====
|
|
223
|
+
getData() {
|
|
224
|
+
return { ...this.data };
|
|
225
|
+
}
|
|
226
|
+
getCurrentMood() {
|
|
227
|
+
return this.calculateMood();
|
|
228
|
+
}
|
|
229
|
+
getLevelProgress() {
|
|
230
|
+
return level_system_1.LevelSystem.getLevelProgress(this.data.level, this.data.experience);
|
|
231
|
+
}
|
|
232
|
+
getXPToNextLevel() {
|
|
233
|
+
return level_system_1.LevelSystem.getXPToNextLevel(this.data.level, this.data.experience);
|
|
234
|
+
}
|
|
235
|
+
getLevelTitle() {
|
|
236
|
+
return level_system_1.LevelSystem.getLevelTitle(this.data.level);
|
|
237
|
+
}
|
|
238
|
+
// ===== TIME SYNC =====
|
|
239
|
+
/**
|
|
240
|
+
* Sync time and apply stat decay based on elapsed time
|
|
241
|
+
* Returns the sync result if changes were applied, null if no significant time passed
|
|
242
|
+
*/
|
|
243
|
+
syncTime() {
|
|
244
|
+
const now = new Date();
|
|
245
|
+
const lastUpdated = new Date(this.data.lastUpdated || this.data.lastSaveTime);
|
|
246
|
+
const hoursPassed = (now.getTime() - lastUpdated.getTime()) / (1000 * 60 * 60);
|
|
247
|
+
// Skip if less than 1 minute passed
|
|
248
|
+
if (hoursPassed < 0.017) {
|
|
249
|
+
return null;
|
|
250
|
+
}
|
|
251
|
+
const result = this.timeDecay.calculateDecay(hoursPassed, this.data.stats, this.data.isSleeping);
|
|
252
|
+
// Apply the decayed stats
|
|
253
|
+
this.data.stats = result.newStats;
|
|
254
|
+
this.updateMood();
|
|
255
|
+
// Update lastUpdated timestamp
|
|
256
|
+
this.data.lastUpdated = now.toISOString();
|
|
257
|
+
// Only save if significant changes occurred
|
|
258
|
+
if (result.statChanges.length > 0) {
|
|
259
|
+
this.save();
|
|
260
|
+
}
|
|
261
|
+
this.synced = true;
|
|
262
|
+
return result;
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Get hours passed since last update
|
|
266
|
+
*/
|
|
267
|
+
getHoursSinceUpdate() {
|
|
268
|
+
const now = new Date();
|
|
269
|
+
const lastUpdated = new Date(this.data.lastUpdated || this.data.lastSaveTime);
|
|
270
|
+
return (now.getTime() - lastUpdated.getTime()) / (1000 * 60 * 60);
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* Check if pet needs care (has critical stats)
|
|
274
|
+
*/
|
|
275
|
+
needsCare() {
|
|
276
|
+
const { stats } = this.data;
|
|
277
|
+
return (stats.hunger < 30 ||
|
|
278
|
+
stats.happiness < 30 ||
|
|
279
|
+
stats.health < 40 ||
|
|
280
|
+
stats.cleanliness < 30 ||
|
|
281
|
+
stats.energy < 20);
|
|
282
|
+
}
|
|
283
|
+
// ===== PERSISTENCE =====
|
|
284
|
+
save() {
|
|
285
|
+
this.data.lastUpdated = new Date().toISOString();
|
|
286
|
+
this.data.inventory = this.inventory.serialize();
|
|
287
|
+
this.db.savePet(this.data);
|
|
288
|
+
}
|
|
289
|
+
// ===== INVENTORY =====
|
|
290
|
+
getInventory() {
|
|
291
|
+
return this.inventory;
|
|
292
|
+
}
|
|
293
|
+
useItem(itemId) {
|
|
294
|
+
const item = this.inventory.getItemQuantity(itemId);
|
|
295
|
+
if (item <= 0) {
|
|
296
|
+
return {
|
|
297
|
+
success: false,
|
|
298
|
+
message: `You don't have this item!`,
|
|
299
|
+
statChanges: [],
|
|
300
|
+
xpGained: 0,
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
// Remove the item
|
|
304
|
+
this.inventory.removeItem(itemId, 1);
|
|
305
|
+
// Get item definition and apply effects
|
|
306
|
+
const itemDef = (0, items_1.getItem)(itemId);
|
|
307
|
+
if (!itemDef) {
|
|
308
|
+
return {
|
|
309
|
+
success: false,
|
|
310
|
+
message: `Unknown item!`,
|
|
311
|
+
statChanges: [],
|
|
312
|
+
xpGained: 0,
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
// Convert item effects to stat changes
|
|
316
|
+
const changes = [];
|
|
317
|
+
if (itemDef.effect.hunger)
|
|
318
|
+
changes.push({ stat: 'hunger', delta: itemDef.effect.hunger });
|
|
319
|
+
if (itemDef.effect.happiness)
|
|
320
|
+
changes.push({ stat: 'happiness', delta: itemDef.effect.happiness });
|
|
321
|
+
if (itemDef.effect.health)
|
|
322
|
+
changes.push({ stat: 'health', delta: itemDef.effect.health });
|
|
323
|
+
if (itemDef.effect.cleanliness)
|
|
324
|
+
changes.push({ stat: 'cleanliness', delta: itemDef.effect.cleanliness });
|
|
325
|
+
if (itemDef.effect.energy)
|
|
326
|
+
changes.push({ stat: 'energy', delta: itemDef.effect.energy });
|
|
327
|
+
// Award coins based on item type
|
|
328
|
+
let coinReason;
|
|
329
|
+
switch (itemDef.type) {
|
|
330
|
+
case items_1.ItemType.FOOD:
|
|
331
|
+
coinReason = shop_1.CoinReason.FEED;
|
|
332
|
+
break;
|
|
333
|
+
case items_1.ItemType.TOY:
|
|
334
|
+
coinReason = shop_1.CoinReason.PLAY;
|
|
335
|
+
break;
|
|
336
|
+
case items_1.ItemType.CLEANING:
|
|
337
|
+
coinReason = shop_1.CoinReason.CLEAN;
|
|
338
|
+
break;
|
|
339
|
+
case items_1.ItemType.MEDICINE:
|
|
340
|
+
coinReason = shop_1.CoinReason.HEAL;
|
|
341
|
+
break;
|
|
342
|
+
default:
|
|
343
|
+
coinReason = shop_1.CoinReason.FEED;
|
|
344
|
+
}
|
|
345
|
+
const coinsEarned = shop_1.Shop.getCoinReward(coinReason);
|
|
346
|
+
if (coinsEarned > 0) {
|
|
347
|
+
this.earnCoins(coinsEarned, coinReason);
|
|
348
|
+
}
|
|
349
|
+
return this.performAction(changes, itemDef.xpReward, `used ${itemDef.name}`);
|
|
350
|
+
}
|
|
351
|
+
addItem(itemId, quantity = 1) {
|
|
352
|
+
this.inventory.addItem(itemId, quantity);
|
|
353
|
+
this.save();
|
|
354
|
+
}
|
|
355
|
+
hasItem(itemId) {
|
|
356
|
+
return this.inventory.hasItem(itemId);
|
|
357
|
+
}
|
|
358
|
+
// ===== COINS =====
|
|
359
|
+
/**
|
|
360
|
+
* Get current coin balance
|
|
361
|
+
*/
|
|
362
|
+
getCoins() {
|
|
363
|
+
return this.data.coins || 0;
|
|
364
|
+
}
|
|
365
|
+
/**
|
|
366
|
+
* Earn coins from an activity
|
|
367
|
+
* @param amount - Number of coins earned
|
|
368
|
+
* @param reason - Source of coins (feed, play, git_commit, etc.)
|
|
369
|
+
*/
|
|
370
|
+
earnCoins(amount, reason) {
|
|
371
|
+
// Initialize coins if not present (for backward compatibility)
|
|
372
|
+
if (typeof this.data.coins !== 'number') {
|
|
373
|
+
this.data.coins = 0;
|
|
374
|
+
}
|
|
375
|
+
if (!Array.isArray(this.data.coinHistory)) {
|
|
376
|
+
this.data.coinHistory = [];
|
|
377
|
+
}
|
|
378
|
+
this.data.coins += amount;
|
|
379
|
+
// Record the coin entry
|
|
380
|
+
const entry = {
|
|
381
|
+
amount,
|
|
382
|
+
reason,
|
|
383
|
+
timestamp: new Date().toISOString(),
|
|
384
|
+
};
|
|
385
|
+
this.data.coinHistory.push(entry);
|
|
386
|
+
// Keep only last 100 entries to save space
|
|
387
|
+
if (this.data.coinHistory.length > 100) {
|
|
388
|
+
this.data.coinHistory = this.data.coinHistory.slice(-100);
|
|
389
|
+
}
|
|
390
|
+
this.save();
|
|
391
|
+
}
|
|
392
|
+
/**
|
|
393
|
+
* Spend coins (returns true if successful)
|
|
394
|
+
* @param amount - Number of coins to spend
|
|
395
|
+
*/
|
|
396
|
+
spendCoins(amount) {
|
|
397
|
+
if (this.getCoins() < amount) {
|
|
398
|
+
return false;
|
|
399
|
+
}
|
|
400
|
+
this.data.coins -= amount;
|
|
401
|
+
// Initialize coinHistory if needed
|
|
402
|
+
if (!Array.isArray(this.data.coinHistory)) {
|
|
403
|
+
this.data.coinHistory = [];
|
|
404
|
+
}
|
|
405
|
+
// Record the spending as a negative entry
|
|
406
|
+
const entry = {
|
|
407
|
+
amount: -amount,
|
|
408
|
+
reason: shop_1.CoinReason.GIFT, // Using GIFT as a generic marker for purchases
|
|
409
|
+
timestamp: new Date().toISOString(),
|
|
410
|
+
};
|
|
411
|
+
this.data.coinHistory.push(entry);
|
|
412
|
+
this.save();
|
|
413
|
+
return true;
|
|
414
|
+
}
|
|
415
|
+
/**
|
|
416
|
+
* Get coins earned today
|
|
417
|
+
*/
|
|
418
|
+
getTodayCoins() {
|
|
419
|
+
if (!Array.isArray(this.data.coinHistory)) {
|
|
420
|
+
return 0;
|
|
421
|
+
}
|
|
422
|
+
const today = new Date().toDateString();
|
|
423
|
+
return this.data.coinHistory
|
|
424
|
+
.filter((entry) => {
|
|
425
|
+
const entryDate = new Date(entry.timestamp).toDateString();
|
|
426
|
+
return entryDate === today && entry.amount > 0;
|
|
427
|
+
})
|
|
428
|
+
.reduce((sum, entry) => sum + entry.amount, 0);
|
|
429
|
+
}
|
|
430
|
+
/**
|
|
431
|
+
* Get total lifetime coins earned
|
|
432
|
+
*/
|
|
433
|
+
getTotalCoinsEarned() {
|
|
434
|
+
if (!Array.isArray(this.data.coinHistory)) {
|
|
435
|
+
return 0;
|
|
436
|
+
}
|
|
437
|
+
return this.data.coinHistory
|
|
438
|
+
.filter((entry) => entry.amount > 0)
|
|
439
|
+
.reduce((sum, entry) => sum + entry.amount, 0);
|
|
440
|
+
}
|
|
441
|
+
// ===== GIT METHODS =====
|
|
442
|
+
/**
|
|
443
|
+
* Get current Git streak
|
|
444
|
+
*/
|
|
445
|
+
getGitStreak() {
|
|
446
|
+
return this.data.gitStreak || 0;
|
|
447
|
+
}
|
|
448
|
+
/**
|
|
449
|
+
* Get total Git commits rewarded
|
|
450
|
+
*/
|
|
451
|
+
getGitCommitCount() {
|
|
452
|
+
return this.data.gitCommitCount || 0;
|
|
453
|
+
}
|
|
454
|
+
/**
|
|
455
|
+
* Get last rewarded commit hash
|
|
456
|
+
*/
|
|
457
|
+
getLastGitCommit() {
|
|
458
|
+
return this.data.lastGitCommit;
|
|
459
|
+
}
|
|
460
|
+
/**
|
|
461
|
+
* Check and reward new Git commits
|
|
462
|
+
* Returns a GitProcessResult with details of what was rewarded
|
|
463
|
+
*/
|
|
464
|
+
async checkGitCommits() {
|
|
465
|
+
const monitor = new git_monitor_1.GitMonitor();
|
|
466
|
+
// Check if Git is available
|
|
467
|
+
const gitAvailable = await git_monitor_1.GitMonitor.isGitAvailable();
|
|
468
|
+
if (!gitAvailable) {
|
|
469
|
+
return {
|
|
470
|
+
success: false,
|
|
471
|
+
newCommits: 0,
|
|
472
|
+
totalCoins: 0,
|
|
473
|
+
totalXP: 0,
|
|
474
|
+
streak: this.getGitStreak(),
|
|
475
|
+
rewards: [],
|
|
476
|
+
error: 'Git is not installed or not available in PATH',
|
|
477
|
+
};
|
|
478
|
+
}
|
|
479
|
+
// Check if we're in a Git repo
|
|
480
|
+
if (!monitor.isInGitRepo()) {
|
|
481
|
+
return {
|
|
482
|
+
success: false,
|
|
483
|
+
newCommits: 0,
|
|
484
|
+
totalCoins: 0,
|
|
485
|
+
totalXP: 0,
|
|
486
|
+
streak: this.getGitStreak(),
|
|
487
|
+
rewards: [],
|
|
488
|
+
error: 'Not a Git repository. Initialize a repo with: git init',
|
|
489
|
+
};
|
|
490
|
+
}
|
|
491
|
+
let newCommits = [];
|
|
492
|
+
// Get commits since last rewarded commit
|
|
493
|
+
if (this.data.lastGitCommit) {
|
|
494
|
+
newCommits = await monitor.getCommitsSince(this.data.lastGitCommit);
|
|
495
|
+
}
|
|
496
|
+
else {
|
|
497
|
+
// First time checking - get all commits
|
|
498
|
+
newCommits = await monitor.getAllCommits(50);
|
|
499
|
+
}
|
|
500
|
+
// Filter out already rewarded commits (by hash)
|
|
501
|
+
const lastHash = this.data.lastGitCommit;
|
|
502
|
+
const trulyNewCommits = lastHash
|
|
503
|
+
? newCommits.filter(c => c.hash !== lastHash && c.hash.startsWith(lastHash) === false)
|
|
504
|
+
: newCommits;
|
|
505
|
+
if (trulyNewCommits.length === 0) {
|
|
506
|
+
return {
|
|
507
|
+
success: true,
|
|
508
|
+
newCommits: 0,
|
|
509
|
+
totalCoins: 0,
|
|
510
|
+
totalXP: 0,
|
|
511
|
+
streak: this.getGitStreak(),
|
|
512
|
+
rewards: [],
|
|
513
|
+
};
|
|
514
|
+
}
|
|
515
|
+
// Process each commit and calculate rewards
|
|
516
|
+
const rewards = [];
|
|
517
|
+
let totalCoins = 0;
|
|
518
|
+
let totalXP = 0;
|
|
519
|
+
// Sort commits by date (oldest first)
|
|
520
|
+
trulyNewCommits.sort((a, b) => a.date.getTime() - b.date.getTime());
|
|
521
|
+
for (const commit of trulyNewCommits) {
|
|
522
|
+
const reward = this.processGitCommit(commit, monitor);
|
|
523
|
+
rewards.push(reward);
|
|
524
|
+
totalCoins += reward.coins;
|
|
525
|
+
totalXP += reward.xp;
|
|
526
|
+
}
|
|
527
|
+
// Update streak based on commit dates
|
|
528
|
+
this.updateGitStreak(trulyNewCommits);
|
|
529
|
+
// Update last rewarded commit (most recent)
|
|
530
|
+
const mostRecent = trulyNewCommits[trulyNewCommits.length - 1];
|
|
531
|
+
this.data.lastGitCommit = mostRecent.shortHash;
|
|
532
|
+
this.data.gitCommitCount = (this.data.gitCommitCount || 0) + trulyNewCommits.length;
|
|
533
|
+
// Add streak bonus coins
|
|
534
|
+
const streakBonus = this.calculateStreakBonus();
|
|
535
|
+
if (streakBonus > 0) {
|
|
536
|
+
totalCoins += streakBonus;
|
|
537
|
+
// Add streak bonus to coin history
|
|
538
|
+
if (streakBonus >= 50) {
|
|
539
|
+
this.earnCoins(streakBonus, shop_1.CoinReason.GIT_STREAK_30);
|
|
540
|
+
}
|
|
541
|
+
else if (streakBonus >= 10) {
|
|
542
|
+
this.earnCoins(streakBonus, shop_1.CoinReason.GIT_STREAK_7);
|
|
543
|
+
}
|
|
544
|
+
else if (streakBonus > 0) {
|
|
545
|
+
this.earnCoins(streakBonus, shop_1.CoinReason.GIT_STREAK_DAILY);
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
this.save();
|
|
549
|
+
return {
|
|
550
|
+
success: true,
|
|
551
|
+
newCommits: trulyNewCommits.length,
|
|
552
|
+
totalCoins,
|
|
553
|
+
totalXP,
|
|
554
|
+
streak: this.getGitStreak(),
|
|
555
|
+
rewards,
|
|
556
|
+
};
|
|
557
|
+
}
|
|
558
|
+
/**
|
|
559
|
+
* Process a single Git commit and return the reward details
|
|
560
|
+
*/
|
|
561
|
+
processGitCommit(commit, monitor) {
|
|
562
|
+
const reward = monitor.calculateReward(commit);
|
|
563
|
+
const bonuses = [];
|
|
564
|
+
// Determine coin reason based on commit type
|
|
565
|
+
let coinReason;
|
|
566
|
+
const baseCoins = reward.baseCoins;
|
|
567
|
+
const baseXP = reward.baseXP;
|
|
568
|
+
switch (reward.commitType) {
|
|
569
|
+
case git_1.GitCommitType.BUG_FIX:
|
|
570
|
+
coinReason = shop_1.CoinReason.GIT_COMMIT_BUG_FIX;
|
|
571
|
+
break;
|
|
572
|
+
case git_1.GitCommitType.FEATURE:
|
|
573
|
+
coinReason = shop_1.CoinReason.GIT_COMMIT_FEATURE;
|
|
574
|
+
break;
|
|
575
|
+
case git_1.GitCommitType.REFACTOR:
|
|
576
|
+
coinReason = shop_1.CoinReason.GIT_COMMIT_REFACTOR;
|
|
577
|
+
break;
|
|
578
|
+
default:
|
|
579
|
+
coinReason = shop_1.CoinReason.GIT_COMMIT_NORMAL;
|
|
580
|
+
break;
|
|
581
|
+
}
|
|
582
|
+
let totalCoins = baseCoins;
|
|
583
|
+
let totalXP = baseXP;
|
|
584
|
+
// Apply night owl bonus (2x coins)
|
|
585
|
+
if (reward.nightBonus) {
|
|
586
|
+
totalCoins = baseCoins * 2;
|
|
587
|
+
bonuses.push('🦉 Night Owl Bonus! 2x coins!');
|
|
588
|
+
}
|
|
589
|
+
// Apply large commit bonus
|
|
590
|
+
if (reward.largeBonus) {
|
|
591
|
+
totalCoins += 5;
|
|
592
|
+
totalXP += 10;
|
|
593
|
+
bonuses.push(`📦 Large Commit Bonus!`);
|
|
594
|
+
this.earnCoins(5, shop_1.CoinReason.GIT_LARGE_BONUS);
|
|
595
|
+
}
|
|
596
|
+
// Award base coins
|
|
597
|
+
this.earnCoins(totalCoins, coinReason);
|
|
598
|
+
// Award XP
|
|
599
|
+
this.addExperience(totalXP);
|
|
600
|
+
// NEW: Apply stat bonuses from Git commit (陪伴效果 - writing code = being with pet)
|
|
601
|
+
const statBonus = this.calculateGitStatBonus();
|
|
602
|
+
this.applyStatDirectly('hunger', statBonus.hunger);
|
|
603
|
+
this.applyStatDirectly('happiness', statBonus.happiness);
|
|
604
|
+
// Add friendly message about pet enjoying the company
|
|
605
|
+
bonuses.push(`💚 ${this.data.name} enjoyed watching you code! (+${statBonus.hunger} hunger, +${statBonus.happiness} happiness)`);
|
|
606
|
+
return {
|
|
607
|
+
shortHash: commit.shortHash,
|
|
608
|
+
message: commit.message,
|
|
609
|
+
type: reward.commitType,
|
|
610
|
+
coins: totalCoins,
|
|
611
|
+
xp: totalXP,
|
|
612
|
+
bonuses,
|
|
613
|
+
};
|
|
614
|
+
}
|
|
615
|
+
/**
|
|
616
|
+
* Calculate stat bonuses from Git commits
|
|
617
|
+
* Philosophy: Writing code = being with your pet
|
|
618
|
+
*/
|
|
619
|
+
calculateGitStatBonus() {
|
|
620
|
+
const streak = this.getGitStreak();
|
|
621
|
+
let hunger = 15; // Base: pet feels fed seeing you work
|
|
622
|
+
let happiness = 10; // Base: pet is happy you're here
|
|
623
|
+
// Streak bonus - consistent coding means better care
|
|
624
|
+
if (streak >= 7) {
|
|
625
|
+
hunger += 5;
|
|
626
|
+
happiness += 5;
|
|
627
|
+
}
|
|
628
|
+
else if (streak >= 3) {
|
|
629
|
+
hunger += 2;
|
|
630
|
+
happiness += 2;
|
|
631
|
+
}
|
|
632
|
+
return { hunger, happiness };
|
|
633
|
+
}
|
|
634
|
+
/**
|
|
635
|
+
* Update Git streak based on commit dates
|
|
636
|
+
*/
|
|
637
|
+
updateGitStreak(commits) {
|
|
638
|
+
if (!this.data.lastGitDate) {
|
|
639
|
+
// First commit - start streak
|
|
640
|
+
const oldestCommit = commits[0];
|
|
641
|
+
this.data.lastGitDate = this.formatDate(oldestCommit.date);
|
|
642
|
+
this.data.gitStreak = 1;
|
|
643
|
+
return;
|
|
644
|
+
}
|
|
645
|
+
const lastDate = new Date(this.data.lastGitDate);
|
|
646
|
+
const oldestCommit = commits[0];
|
|
647
|
+
const commitDate = new Date(this.formatDate(oldestCommit.date));
|
|
648
|
+
// Calculate day difference
|
|
649
|
+
const dayDiff = Math.round((commitDate.getTime() - lastDate.getTime()) / (1000 * 60 * 60 * 24));
|
|
650
|
+
if (dayDiff === 0) {
|
|
651
|
+
// Same day - streak continues, don't increment
|
|
652
|
+
return;
|
|
653
|
+
}
|
|
654
|
+
else if (dayDiff === 1) {
|
|
655
|
+
// Next day - increment streak
|
|
656
|
+
this.data.gitStreak = (this.data.gitStreak || 0) + 1;
|
|
657
|
+
}
|
|
658
|
+
else {
|
|
659
|
+
// Streak broken - start new streak
|
|
660
|
+
this.data.gitStreak = 1;
|
|
661
|
+
}
|
|
662
|
+
this.data.lastGitDate = this.formatDate(oldestCommit.date);
|
|
663
|
+
}
|
|
664
|
+
/**
|
|
665
|
+
* Calculate streak bonus coins
|
|
666
|
+
*/
|
|
667
|
+
calculateStreakBonus() {
|
|
668
|
+
const streak = this.getGitStreak();
|
|
669
|
+
if (streak >= 30) {
|
|
670
|
+
return 50; // Major milestone bonus
|
|
671
|
+
}
|
|
672
|
+
else if (streak >= 7) {
|
|
673
|
+
return 10; // Week streak bonus
|
|
674
|
+
}
|
|
675
|
+
else if (streak > 1) {
|
|
676
|
+
return streak; // 1 coin per streak day
|
|
677
|
+
}
|
|
678
|
+
return 0;
|
|
679
|
+
}
|
|
680
|
+
/**
|
|
681
|
+
* Format date as YYYY-MM-DD
|
|
682
|
+
*/
|
|
683
|
+
formatDate(date) {
|
|
684
|
+
const d = new Date(date);
|
|
685
|
+
return d.toISOString().split('T')[0];
|
|
686
|
+
}
|
|
687
|
+
// ===== AUTO-CARE METHODS =====
|
|
688
|
+
/**
|
|
689
|
+
* Get auto-care suggestions based on pet state
|
|
690
|
+
*/
|
|
691
|
+
getSuggestions() {
|
|
692
|
+
const autoCare = (0, auto_care_1.createAutoCare)();
|
|
693
|
+
return autoCare.getSuggestions(this);
|
|
694
|
+
}
|
|
695
|
+
/**
|
|
696
|
+
* Apply stat change directly (for Git commit bonuses, etc.)
|
|
697
|
+
* This bypasses the normal action flow for internal stat adjustments
|
|
698
|
+
*/
|
|
699
|
+
applyStatDirectly(stat, value) {
|
|
700
|
+
this.data.stats[stat] = (0, stats_1.clampStat)(this.data.stats[stat] + value);
|
|
701
|
+
this.updateMood();
|
|
702
|
+
this.save();
|
|
703
|
+
}
|
|
704
|
+
// ===== PRIVATE METHODS =====
|
|
705
|
+
performAction(changes, xpGained, actionName) {
|
|
706
|
+
const oldLevel = this.data.level;
|
|
707
|
+
this.applyStatChanges(changes);
|
|
708
|
+
this.addExperience(xpGained);
|
|
709
|
+
this.updateMood();
|
|
710
|
+
this.updateInteraction();
|
|
711
|
+
this.save();
|
|
712
|
+
const newLevel = this.data.level;
|
|
713
|
+
const levelUp = newLevel > oldLevel;
|
|
714
|
+
let message = `${this.data.name} enjoyed being ${actionName}!`;
|
|
715
|
+
if (levelUp) {
|
|
716
|
+
message = `Level up! ${this.data.name} is now level ${newLevel}! 🎉`;
|
|
717
|
+
}
|
|
718
|
+
return {
|
|
719
|
+
success: true,
|
|
720
|
+
message,
|
|
721
|
+
statChanges: changes,
|
|
722
|
+
xpGained,
|
|
723
|
+
levelUp,
|
|
724
|
+
newLevel,
|
|
725
|
+
};
|
|
726
|
+
}
|
|
727
|
+
applyStatChanges(changes) {
|
|
728
|
+
for (const change of changes) {
|
|
729
|
+
const currentValue = this.data.stats[change.stat];
|
|
730
|
+
this.data.stats[change.stat] = (0, stats_1.clampStat)(currentValue + change.delta);
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
addExperience(amount) {
|
|
734
|
+
this.data.experience += amount;
|
|
735
|
+
// Check for level up
|
|
736
|
+
const newLevel = level_system_1.LevelSystem.calculateNewLevel(this.data.level, this.data.experience);
|
|
737
|
+
if (newLevel > this.data.level) {
|
|
738
|
+
this.data.level = newLevel;
|
|
739
|
+
// Bonus stats on level up
|
|
740
|
+
this.data.stats = {
|
|
741
|
+
hunger: (0, stats_1.clampStat)(this.data.stats.hunger + 5),
|
|
742
|
+
happiness: (0, stats_1.clampStat)(this.data.stats.happiness + 5),
|
|
743
|
+
health: (0, stats_1.clampStat)(this.data.stats.health + 5),
|
|
744
|
+
cleanliness: (0, stats_1.clampStat)(this.data.stats.cleanliness + 5),
|
|
745
|
+
energy: (0, stats_1.clampStat)(this.data.stats.energy + 5),
|
|
746
|
+
};
|
|
747
|
+
// Award coins on level up
|
|
748
|
+
const levelUpBonus = shop_1.Shop.getCoinReward(shop_1.CoinReason.LEVEL_UP);
|
|
749
|
+
this.earnCoins(levelUpBonus, shop_1.CoinReason.LEVEL_UP);
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
calculateMood() {
|
|
753
|
+
if (this.data.isSleeping) {
|
|
754
|
+
return species_1.MoodState.SLEEPY;
|
|
755
|
+
}
|
|
756
|
+
const { stats } = this.data;
|
|
757
|
+
if (stats.health < 30)
|
|
758
|
+
return species_1.MoodState.SICK;
|
|
759
|
+
if (stats.energy < 20)
|
|
760
|
+
return species_1.MoodState.SLEEPY;
|
|
761
|
+
if (stats.hunger < 20)
|
|
762
|
+
return species_1.MoodState.ANGRY;
|
|
763
|
+
const avgStat = (stats.happiness + stats.hunger + stats.cleanliness) / 3;
|
|
764
|
+
if (avgStat >= 80 && stats.energy > 60)
|
|
765
|
+
return species_1.MoodState.EXCITED;
|
|
766
|
+
if (avgStat >= 60)
|
|
767
|
+
return species_1.MoodState.HAPPY;
|
|
768
|
+
if (avgStat >= 40)
|
|
769
|
+
return species_1.MoodState.SAD;
|
|
770
|
+
return species_1.MoodState.ANGRY;
|
|
771
|
+
}
|
|
772
|
+
updateMood() {
|
|
773
|
+
this.data.mood = this.calculateMood();
|
|
774
|
+
}
|
|
775
|
+
updateInteraction() {
|
|
776
|
+
this.data.lastInteraction = new Date().toISOString();
|
|
777
|
+
this.data.totalInteractions++;
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
exports.Pet = Pet;
|
|
781
|
+
//# sourceMappingURL=pet.js.map
|