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.
Files changed (147) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +268 -0
  3. package/dist/commands/care.d.ts +3 -0
  4. package/dist/commands/care.d.ts.map +1 -0
  5. package/dist/commands/care.js +103 -0
  6. package/dist/commands/care.js.map +1 -0
  7. package/dist/commands/clean.d.ts +3 -0
  8. package/dist/commands/clean.d.ts.map +1 -0
  9. package/dist/commands/clean.js +105 -0
  10. package/dist/commands/clean.js.map +1 -0
  11. package/dist/commands/feed.d.ts +3 -0
  12. package/dist/commands/feed.d.ts.map +1 -0
  13. package/dist/commands/feed.js +106 -0
  14. package/dist/commands/feed.js.map +1 -0
  15. package/dist/commands/git.d.ts +3 -0
  16. package/dist/commands/git.d.ts.map +1 -0
  17. package/dist/commands/git.js +138 -0
  18. package/dist/commands/git.js.map +1 -0
  19. package/dist/commands/heal.d.ts +3 -0
  20. package/dist/commands/heal.d.ts.map +1 -0
  21. package/dist/commands/heal.js +106 -0
  22. package/dist/commands/heal.js.map +1 -0
  23. package/dist/commands/init.d.ts +3 -0
  24. package/dist/commands/init.d.ts.map +1 -0
  25. package/dist/commands/init.js +89 -0
  26. package/dist/commands/init.js.map +1 -0
  27. package/dist/commands/inventory.d.ts +3 -0
  28. package/dist/commands/inventory.d.ts.map +1 -0
  29. package/dist/commands/inventory.js +113 -0
  30. package/dist/commands/inventory.js.map +1 -0
  31. package/dist/commands/play.d.ts +3 -0
  32. package/dist/commands/play.d.ts.map +1 -0
  33. package/dist/commands/play.js +110 -0
  34. package/dist/commands/play.js.map +1 -0
  35. package/dist/commands/shop.d.ts +3 -0
  36. package/dist/commands/shop.d.ts.map +1 -0
  37. package/dist/commands/shop.js +197 -0
  38. package/dist/commands/shop.js.map +1 -0
  39. package/dist/commands/sleep.d.ts +3 -0
  40. package/dist/commands/sleep.d.ts.map +1 -0
  41. package/dist/commands/sleep.js +48 -0
  42. package/dist/commands/sleep.js.map +1 -0
  43. package/dist/commands/status.d.ts +3 -0
  44. package/dist/commands/status.d.ts.map +1 -0
  45. package/dist/commands/status.js +98 -0
  46. package/dist/commands/status.js.map +1 -0
  47. package/dist/commands/sync.d.ts +3 -0
  48. package/dist/commands/sync.d.ts.map +1 -0
  49. package/dist/commands/sync.js +66 -0
  50. package/dist/commands/sync.js.map +1 -0
  51. package/dist/commands/tutorial.d.ts +3 -0
  52. package/dist/commands/tutorial.d.ts.map +1 -0
  53. package/dist/commands/tutorial.js +210 -0
  54. package/dist/commands/tutorial.js.map +1 -0
  55. package/dist/core/auto-care.d.ts +63 -0
  56. package/dist/core/auto-care.d.ts.map +1 -0
  57. package/dist/core/auto-care.js +295 -0
  58. package/dist/core/auto-care.js.map +1 -0
  59. package/dist/core/config.d.ts +89 -0
  60. package/dist/core/config.d.ts.map +1 -0
  61. package/dist/core/config.js +195 -0
  62. package/dist/core/config.js.map +1 -0
  63. package/dist/core/database.d.ts +29 -0
  64. package/dist/core/database.d.ts.map +1 -0
  65. package/dist/core/database.js +81 -0
  66. package/dist/core/database.js.map +1 -0
  67. package/dist/core/inventory.d.ts +63 -0
  68. package/dist/core/inventory.d.ts.map +1 -0
  69. package/dist/core/inventory.js +140 -0
  70. package/dist/core/inventory.js.map +1 -0
  71. package/dist/core/level-system.d.ts +32 -0
  72. package/dist/core/level-system.d.ts.map +1 -0
  73. package/dist/core/level-system.js +76 -0
  74. package/dist/core/level-system.js.map +1 -0
  75. package/dist/core/pet.d.ts +123 -0
  76. package/dist/core/pet.d.ts.map +1 -0
  77. package/dist/core/pet.js +781 -0
  78. package/dist/core/pet.js.map +1 -0
  79. package/dist/core/shop.d.ts +93 -0
  80. package/dist/core/shop.d.ts.map +1 -0
  81. package/dist/core/shop.js +205 -0
  82. package/dist/core/shop.js.map +1 -0
  83. package/dist/core/time-decay.d.ts +30 -0
  84. package/dist/core/time-decay.d.ts.map +1 -0
  85. package/dist/core/time-decay.js +149 -0
  86. package/dist/core/time-decay.js.map +1 -0
  87. package/dist/core/welcome.d.ts +31 -0
  88. package/dist/core/welcome.d.ts.map +1 -0
  89. package/dist/core/welcome.js +180 -0
  90. package/dist/core/welcome.js.map +1 -0
  91. package/dist/index.d.ts +3 -0
  92. package/dist/index.d.ts.map +1 -0
  93. package/dist/index.js +80 -0
  94. package/dist/index.js.map +1 -0
  95. package/dist/monitor/git-monitor.d.ts +90 -0
  96. package/dist/monitor/git-monitor.d.ts.map +1 -0
  97. package/dist/monitor/git-monitor.js +342 -0
  98. package/dist/monitor/git-monitor.js.map +1 -0
  99. package/dist/types/auto-care.d.ts +86 -0
  100. package/dist/types/auto-care.d.ts.map +1 -0
  101. package/dist/types/auto-care.js +15 -0
  102. package/dist/types/auto-care.js.map +1 -0
  103. package/dist/types/config.d.ts +42 -0
  104. package/dist/types/config.d.ts.map +1 -0
  105. package/dist/types/config.js +24 -0
  106. package/dist/types/config.js.map +1 -0
  107. package/dist/types/git.d.ts +68 -0
  108. package/dist/types/git.d.ts.map +1 -0
  109. package/dist/types/git.js +14 -0
  110. package/dist/types/git.js.map +1 -0
  111. package/dist/types/items.d.ts +41 -0
  112. package/dist/types/items.d.ts.map +1 -0
  113. package/dist/types/items.js +286 -0
  114. package/dist/types/items.js.map +1 -0
  115. package/dist/types/pet.d.ts +42 -0
  116. package/dist/types/pet.d.ts.map +1 -0
  117. package/dist/types/pet.js +16 -0
  118. package/dist/types/pet.js.map +1 -0
  119. package/dist/types/species.d.ts +30 -0
  120. package/dist/types/species.d.ts.map +1 -0
  121. package/dist/types/species.js +164 -0
  122. package/dist/types/species.js.map +1 -0
  123. package/dist/types/stats.d.ts +22 -0
  124. package/dist/types/stats.d.ts.map +1 -0
  125. package/dist/types/stats.js +18 -0
  126. package/dist/types/stats.js.map +1 -0
  127. package/dist/types/time.d.ts +30 -0
  128. package/dist/types/time.d.ts.map +1 -0
  129. package/dist/types/time.js +79 -0
  130. package/dist/types/time.js.map +1 -0
  131. package/dist/ui/ascii-art.d.ts +4 -0
  132. package/dist/ui/ascii-art.d.ts.map +1 -0
  133. package/dist/ui/ascii-art.js +170 -0
  134. package/dist/ui/ascii-art.js.map +1 -0
  135. package/dist/ui/display.d.ts +11 -0
  136. package/dist/ui/display.d.ts.map +1 -0
  137. package/dist/ui/display.js +93 -0
  138. package/dist/ui/display.js.map +1 -0
  139. package/dist/ui/help.d.ts +17 -0
  140. package/dist/ui/help.d.ts.map +1 -0
  141. package/dist/ui/help.js +280 -0
  142. package/dist/ui/help.js.map +1 -0
  143. package/dist/ui/progress-bar.d.ts +9 -0
  144. package/dist/ui/progress-bar.d.ts.map +1 -0
  145. package/dist/ui/progress-bar.js +57 -0
  146. package/dist/ui/progress-bar.js.map +1 -0
  147. package/package.json +61 -0
@@ -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