/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
var Animal = Container.expand(function (animalType) {
var self = Container.call(this);
self.animalType = animalType || 'deer';
self.graphics = self.attachAsset(self.animalType, {
anchorX: 0.5,
anchorY: 0.5
});
// Animal properties
self.health = 100;
self.alertLevel = 0;
self.speed = 0.5 + Math.random() * 1.5;
self.direction = Math.random() * Math.PI * 2;
self.isAlive = true;
self.detectionRadius = 150 + Math.random() * 100;
self.lastX = self.x;
self.lastY = self.y;
// Visibility properties for dog tracking system
self.isDiscoveredByDog = false;
self.graphics.visible = false; // Start invisible
self.visibilityTimer = 0; // Timer for how long animal has been visible
self.maxVisibilityTime = 300; // 5 seconds at 60fps before going invisible
self.isFadingOut = false; // Flag to prevent multiple fade animations
// Movement timer
self.moveTimer = 0;
self.moveChangeInterval = 60 + Math.random() * 120;
self.update = function () {
if (!self.isAlive) {
return;
}
// Random movement behavior
self.moveTimer++;
if (self.moveTimer >= self.moveChangeInterval) {
self.direction += (Math.random() - 0.5) * 0.5;
self.moveTimer = 0;
self.moveChangeInterval = 60 + Math.random() * 120;
}
// Move animal
self.x += Math.cos(self.direction) * self.speed;
self.y += Math.sin(self.direction) * self.speed;
// Keep within bounds
if (self.x < 50) {
self.x = 50;
self.direction = Math.random() * Math.PI * 2;
}
if (self.x > 1998) {
self.x = 1998;
self.direction = Math.random() * Math.PI * 2;
}
if (self.y < 50) {
self.y = 50;
self.direction = Math.random() * Math.PI * 2;
}
if (self.y > 2682) {
self.y = 2682;
self.direction = Math.random() * Math.PI * 2;
}
// Dog and player detection behavior
var distanceToDog = Math.sqrt(Math.pow(self.x - huntingDog.x, 2) + Math.pow(self.y - huntingDog.y, 2));
var distanceToPlayer = Math.sqrt(Math.pow(self.x - hunter.x, 2) + Math.pow(self.y - hunter.y, 2));
// If discovered by dog, flee from dog
if (self.isDiscoveredByDog && distanceToDog < 300) {
// Flee from dog
var fleeFromDogAngle = Math.atan2(self.y - huntingDog.y, self.x - huntingDog.x);
self.direction = fleeFromDogAngle;
self.speed = Math.min(4, self.speed + 0.15);
self.graphics.alpha = 1.0;
} else if (distanceToPlayer < self.detectionRadius) {
// Normal player detection behavior
self.alertLevel = Math.min(100, self.alertLevel + 2);
// Flee from player
var fleeAngle = Math.atan2(self.y - hunter.y, self.x - hunter.x);
self.direction = fleeAngle;
self.speed = Math.min(4, self.speed + 0.1);
self.graphics.alpha = 1.0; // Fully visible when fleeing from player
} else {
self.alertLevel = Math.max(0, self.alertLevel - 1);
self.speed = Math.max(1, self.speed - 0.05);
self.graphics.alpha = 1.0; // Fully visible when calm
}
// Handle visibility timer - make animal invisible after being seen for some time
// But don't turn invisible if being chased by the dog
var isBeingChased = distanceToDog < 300 && self.isDiscoveredByDog;
if (self.isDiscoveredByDog && self.graphics.visible && !isBeingChased) {
self.visibilityTimer++;
// Start fading out when timer reaches max visibility time
if (self.visibilityTimer >= self.maxVisibilityTime && !self.isFadingOut) {
self.isFadingOut = true;
// Use tween to fade out the animal
tween(self.graphics, {
alpha: 0
}, {
duration: 1000,
easing: tween.easeOut,
onFinish: function onFinish() {
self.graphics.visible = false;
self.isDiscoveredByDog = false;
self.visibilityTimer = 0;
self.isFadingOut = false;
self.graphics.alpha = 1.0; // Reset alpha for next discovery
}
});
}
} else if (isBeingChased) {
// Reset visibility timer while being chased to prevent fading
self.visibilityTimer = 0;
self.isFadingOut = false;
// Stop any ongoing fade animation
tween.stop(self.graphics, {
alpha: true
});
self.graphics.alpha = 1.0;
}
// Create footprints occasionally - more frequent when fleeing
var footprintChance = 0.02;
if (self.alertLevel > 50 || self.isDiscoveredByDog) {
footprintChance = 0.08; // More footprints when scared or being chased
}
if (Math.random() < footprintChance) {
createFootprint(self.x, self.y);
}
// Play rustling sound when animal is moving and alert
if (self.alertLevel > 30 && Math.random() < 0.01) {
LK.getSound('rustle').play();
}
};
return self;
});
var Duck = Container.expand(function () {
var self = Container.call(this);
self.graphics = self.attachAsset('duck', {
anchorX: 0.5,
anchorY: 0.5
});
// Duck properties
self.health = 100;
self.speed = 1.0;
self.direction = Math.random() * Math.PI * 2;
self.isAlive = true;
self.swimRadius = 80; // How far from pond center duck will swim
self.pondX = 0; // Will be set when duck is assigned to pond
self.pondY = 0;
self.isDiscoveredByDog = false;
self.graphics.visible = false;
self.visibilityTimer = 0;
self.maxVisibilityTime = 400;
self.isFadingOut = false;
self.moveTimer = 0;
self.moveChangeInterval = 90 + Math.random() * 60;
self.update = function () {
if (!self.isAlive) {
return;
}
// Duck swimming behavior - stay near pond center
self.moveTimer++;
if (self.moveTimer >= self.moveChangeInterval) {
// Change direction occasionally
self.direction += (Math.random() - 0.5) * 0.8;
self.moveTimer = 0;
self.moveChangeInterval = 90 + Math.random() * 60;
}
// Move duck
self.x += Math.cos(self.direction) * self.speed;
self.y += Math.sin(self.direction) * self.speed;
// Keep duck within pond area
var distanceFromPondCenter = Math.sqrt(Math.pow(self.x - self.pondX, 2) + Math.pow(self.y - self.pondY, 2));
if (distanceFromPondCenter > self.swimRadius) {
// Turn back toward pond center
var angleToPondCenter = Math.atan2(self.pondY - self.y, self.pondX - self.x);
self.direction = angleToPondCenter + (Math.random() - 0.5) * 0.5;
// Move back toward center
self.x += Math.cos(angleToPondCenter) * 2;
self.y += Math.sin(angleToPondCenter) * 2;
}
// Dog and player detection behavior
var distanceToDog = Math.sqrt(Math.pow(self.x - huntingDog.x, 2) + Math.pow(self.y - huntingDog.y, 2));
var distanceToPlayer = Math.sqrt(Math.pow(self.x - hunter.x, 2) + Math.pow(self.y - hunter.y, 2));
// If discovered by dog, stay more visible and swim faster
if (self.isDiscoveredByDog && distanceToDog < 200) {
self.speed = Math.min(2, self.speed + 0.1);
self.graphics.alpha = 1.0;
} else if (distanceToPlayer < 120) {
// Player detection - swim away from player
var fleeAngle = Math.atan2(self.y - hunter.y, self.x - hunter.x);
self.direction = fleeAngle;
self.speed = Math.min(2.5, self.speed + 0.1);
self.graphics.alpha = 1.0;
} else {
self.speed = Math.max(1, self.speed - 0.05);
self.graphics.alpha = 1.0;
}
// Handle visibility like other animals
var isBeingChased = distanceToDog < 200 && self.isDiscoveredByDog;
if (self.isDiscoveredByDog && self.graphics.visible && !isBeingChased) {
self.visibilityTimer++;
if (self.visibilityTimer >= self.maxVisibilityTime && !self.isFadingOut) {
self.isFadingOut = true;
tween(self.graphics, {
alpha: 0
}, {
duration: 1200,
easing: tween.easeOut,
onFinish: function onFinish() {
self.graphics.visible = false;
self.isDiscoveredByDog = false;
self.visibilityTimer = 0;
self.isFadingOut = false;
self.graphics.alpha = 1.0;
}
});
}
} else if (isBeingChased) {
self.visibilityTimer = 0;
self.isFadingOut = false;
tween.stop(self.graphics, {
alpha: true
});
self.graphics.alpha = 1.0;
}
// Create splash effects occasionally
if (Math.random() < 0.01) {
// Create small ripple effect (using footprint as placeholder)
createFootprint(self.x + (Math.random() - 0.5) * 20, self.y + (Math.random() - 0.5) * 20);
}
};
return self;
});
var Footprint = Container.expand(function () {
var self = Container.call(this);
self.graphics = self.attachAsset('footprint', {
anchorX: 0.5,
anchorY: 0.5
});
self.graphics.alpha = 0.1;
self.lifetime = 600; // 10 seconds at 60fps
self.update = function () {
self.lifetime--;
self.graphics.alpha = self.lifetime / 600 * 0.1;
if (self.lifetime <= 0) {
self.destroy();
for (var i = footprints.length - 1; i >= 0; i--) {
if (footprints[i] === self) {
footprints.splice(i, 1);
break;
}
}
}
};
return self;
});
// Game variables
var Hunter = Container.expand(function () {
var self = Container.call(this);
self.graphics = self.attachAsset('hunter', {
anchorX: 0.5,
anchorY: 0.5
});
self.rifle = self.addChild(LK.getAsset('rifle', {
anchorX: 0,
anchorY: 0.5
}));
self.rifle.x = 30;
self.rifle.y = 0;
// Hunter stats
self.ammo = 30;
self.maxAmmo = 30;
self.hunger = 100;
self.accuracy = 50;
self.stealth = 50;
self.tracking = 50;
self.isMoving = false;
self.lastMoveTime = 0;
return self;
});
var HuntingDog = Container.expand(function () {
var self = Container.call(this);
self.graphics = self.attachAsset('dog', {
anchorX: 0.5,
anchorY: 0.5
});
// Dog properties - will be updated based on breed
self.energy = 100;
self.maxEnergy = 100;
self.loyalty = 80;
self.tracking = 60;
self.speed = 2.5;
self.followDistance = 80;
self.detectionRadius = 200;
self.breed = 'Retriever';
self.specialAbility = 'tracking';
self.isFollowing = true;
self.targetAnimal = null;
self.alertCooldown = 0;
self.wasChasing = false; // Track if dog was previously chasing
self.persistentTarget = null; // Animal that dog will pursue until shot
self.lastKnownTargetX = 0; // Last known position of target
self.lastKnownTargetY = 0;
// Movement properties
self.targetX = 0;
self.targetY = 0;
self.lastX = self.x;
self.lastY = self.y;
// Walking animation properties
self.isWalking = false;
self.walkAnimationOffset = 0;
self.walkAnimationSpeed = 0.2;
self.update = function () {
if (self.energy <= 0) {
return;
}
// Single target priority system - only chase one animal at a time
var targetAnimal = null;
var targetDistance = Infinity;
// First priority: continue chasing persistent target if it exists and is alive
if (self.persistentTarget && self.persistentTarget.isAlive && self.persistentTarget.isDiscoveredByDog) {
var persistentDistance = Math.sqrt(Math.pow(self.x - self.persistentTarget.x, 2) + Math.pow(self.y - self.persistentTarget.y, 2));
if (persistentDistance < 400) {
// Extended chase range
targetAnimal = self.persistentTarget;
targetDistance = persistentDistance;
} else {
// Clear persistent target if too far away
self.persistentTarget = null;
}
}
// Second priority: if no persistent target, find closest discovered animal
if (!targetAnimal) {
var allAnimals = animals.concat(ducks);
for (var a = 0; a < allAnimals.length; a++) {
var animal = allAnimals[a];
if (!animal.isAlive || !animal.isDiscoveredByDog) {
continue;
}
var animalDistance = Math.sqrt(Math.pow(self.x - animal.x, 2) + Math.pow(self.y - animal.y, 2));
if (animalDistance < 250 && animalDistance < targetDistance) {
targetAnimal = animal;
targetDistance = animalDistance;
// Set as new persistent target
self.persistentTarget = animal;
}
}
}
// Use single target for all chase logic
var closestAnimal = targetAnimal;
var closestAnimalDistance = targetDistance;
// Chase behavior - prioritize chasing animals over following hunter
// Expanded detection range for persistent chasing
if (closestAnimal && closestAnimalDistance < 400) {
// Increased range from 250 to 400
// Set chasing flag
self.wasChasing = true;
// Stop any existing return tween
tween.stop(self, {
x: true,
y: true
});
// Strategic herding behavior to drive animal toward player
var animalToHunterAngle = Math.atan2(hunter.y - closestAnimal.y, hunter.x - closestAnimal.x);
var dogToAnimalAngle = Math.atan2(closestAnimal.y - self.y, closestAnimal.x - self.x);
var animalToHunterDistance = Math.sqrt(Math.pow(closestAnimal.x - hunter.x, 2) + Math.pow(closestAnimal.y - hunter.y, 2));
// Auto-shooting when dog is herding animal close to hunter
if (animalToHunterDistance < 250) {
// Animal is close to hunter
// Check if hunter has clear line of sight to animal
var shotBlocked = false;
var dx = closestAnimal.x - hunter.x;
var dy = closestAnimal.y - hunter.y;
var shotLength = Math.sqrt(dx * dx + dy * dy);
var shotDirX = dx / shotLength;
var shotDirY = dy / shotLength;
// Check for tree obstacles
for (var t = 0; t < trees.length; t++) {
var tree = trees[t];
for (var step = 0; step < shotLength; step += 10) {
var checkX = hunter.x + shotDirX * step;
var checkY = hunter.y + shotDirY * step;
var treeDistance = Math.sqrt(Math.pow(checkX - tree.x, 2) + Math.pow(checkY - tree.y, 2));
if (treeDistance < 50) {
shotBlocked = true;
break;
}
}
if (shotBlocked) break;
}
// Auto-shoot if clear shot and within range (every 60 ticks = 1 second)
if (!shotBlocked && shotLength < 300 && LK.ticks % 60 === 0) {
shootAtTarget(closestAnimal.x, closestAnimal.y);
}
}
// Position dog on the opposite side of animal from hunter to herd it toward player
var herdingAngle = animalToHunterAngle + Math.PI; // Opposite direction from hunter
var optimalDistance = 80; // Distance to maintain from animal while herding
// Calculate where dog should be to herd animal toward hunter
var targetHerdX = closestAnimal.x + Math.cos(herdingAngle) * optimalDistance;
var targetHerdY = closestAnimal.y + Math.sin(herdingAngle) * optimalDistance;
// Move toward herding position
var moveToHerdAngle = Math.atan2(targetHerdY - self.y, targetHerdX - self.x);
var distanceToHerdPosition = Math.sqrt(Math.pow(targetHerdX - self.x, 2) + Math.pow(targetHerdY - self.y, 2));
// If dog is far from optimal herding position, move toward it
if (distanceToHerdPosition > 30) {
// Check if dog is in pond and reduce speed
var dogHerdSpeed = self.speed * 1.5;
for (var p = 0; p < ponds.length; p++) {
var pond = ponds[p];
var distanceToPond = Math.sqrt(Math.pow(self.x - pond.x, 2) + Math.pow(self.y - pond.y, 2));
if (distanceToPond < 100) {
// Dog is in or near pond
dogHerdSpeed *= 0.4; // Reduce speed significantly in water
break;
}
}
self.x += Math.cos(moveToHerdAngle) * dogHerdSpeed;
self.y += Math.sin(moveToHerdAngle) * dogHerdSpeed;
} else {
// If in good herding position, apply pressure to drive animal toward hunter
var pressureAngle = Math.atan2(closestAnimal.y - self.y, closestAnimal.x - self.x);
// Check if dog is in pond and reduce speed
var dogChaseSpeed = self.speed * 1.0;
for (var p = 0; p < ponds.length; p++) {
var pond = ponds[p];
var distanceToPond = Math.sqrt(Math.pow(self.x - pond.x, 2) + Math.pow(self.y - pond.y, 2));
if (distanceToPond < 100) {
// Dog is in or near pond
dogChaseSpeed *= 0.4; // Reduce speed significantly in water
break;
}
}
self.x += Math.cos(pressureAngle) * dogChaseSpeed;
self.y += Math.sin(pressureAngle) * dogChaseSpeed;
}
// Point towards the animal being herded
self.graphics.rotation = dogToAnimalAngle;
// Bark more frequently when herding toward player (closer to hunter)
var barkFrequency = animalToHunterDistance < 200 ? 45 : 60; // More frequent barking
if (LK.ticks % barkFrequency === 0) {
LK.getSound('bark').play();
}
// Consume more energy while chasing
if (LK.ticks % 20 === 0) {
// Reduced energy consumption frequency from 15 to 20
self.energy = Math.max(0, self.energy - 0.3); // Reduced energy drain from 0.5 to 0.3
}
// Flash the dog to show it's in chase mode - green flash when successfully herding toward player
if (LK.ticks % 30 === 0) {
var flashColor = animalToHunterDistance < 150 ? 0x44FF44 : 0xFF4444; // Green when close to hunter, red otherwise
LK.effects.flashObject(self, flashColor, 150);
}
}
// If no animals are in immediate chase range, search for persistent target within larger radius
else if (!closestAnimal && self.persistentTarget && self.persistentTarget.isAlive) {
var searchDistance = Math.sqrt(Math.pow(self.x - self.persistentTarget.x, 2) + Math.pow(self.y - self.persistentTarget.y, 2));
if (searchDistance < 600) {
// Large search radius for persistent tracking
// Move directly toward the persistent target
var directAngle = Math.atan2(self.persistentTarget.y - self.y, self.persistentTarget.x - self.x);
// Check if dog is in pond and reduce speed
var dogPursuitSpeed = self.speed * 1.2;
for (var p = 0; p < ponds.length; p++) {
var pond = ponds[p];
var distanceToPond = Math.sqrt(Math.pow(self.x - pond.x, 2) + Math.pow(self.y - pond.y, 2));
if (distanceToPond < 100) {
// Dog is in or near pond
dogPursuitSpeed *= 0.4; // Reduce speed significantly in water
break;
}
}
self.x += Math.cos(directAngle) * dogPursuitSpeed;
self.y += Math.sin(directAngle) * dogPursuitSpeed;
self.graphics.rotation = directAngle;
self.wasChasing = true;
// Bark occasionally to signal pursuit
if (LK.ticks % 90 === 0) {
LK.getSound('bark').play();
}
// Flash to show active pursuit
if (LK.ticks % 60 === 0) {
LK.effects.flashObject(self, 0xFFAA00, 100); // Orange flash for long-distance pursuit
}
} else {
// Clear persistent target if too far away
self.persistentTarget = null;
}
} else if (self.isFollowing) {
// Follow footprints or hunter when not chasing
var distanceToHunter = Math.sqrt(Math.pow(self.x - hunter.x, 2) + Math.pow(self.y - hunter.y, 2));
// If dog was chasing but now stopped, smoothly return to player using tween
if (self.wasChasing && !closestAnimal) {
self.wasChasing = false;
// Use tween to smoothly move dog back to hunter
tween(self, {
x: hunter.x + self.followDistance * 0.6,
y: hunter.y + self.followDistance * 0.4
}, {
duration: 1500,
easing: tween.easeOut,
onFinish: function onFinish() {
// Dog has returned to player
LK.effects.flashObject(self, 0x00FF00, 300);
}
});
return; // Skip normal movement while tweening back
}
// Check for nearby footprints to follow and find hidden animals
var nearestFootprint = null;
var nearestFootprintDistance = Infinity;
var footprintTrail = []; // Store footprints for trail following
for (var f = 0; f < footprints.length; f++) {
var footprint = footprints[f];
var footprintDistance = Math.sqrt(Math.pow(self.x - footprint.x, 2) + Math.pow(self.y - footprint.y, 2));
if (footprintDistance < 150) {
footprintTrail.push({
footprint: footprint,
distance: footprintDistance
});
}
if (footprintDistance < nearestFootprintDistance) {
nearestFootprint = footprint;
nearestFootprintDistance = footprintDistance;
}
}
// Sort footprint trail by distance for better pathfinding
footprintTrail.sort(function (a, b) {
return a.distance - b.distance;
});
// Follow footprints if found, otherwise follow hunter
if (nearestFootprint && nearestFootprintDistance < 80) {
// Move towards footprint
var angle = Math.atan2(nearestFootprint.y - self.y, nearestFootprint.x - self.x);
// Check if dog is in pond and reduce speed
var dogSpeed = self.speed * 0.9;
for (var p = 0; p < ponds.length; p++) {
var pond = ponds[p];
var distanceToPond = Math.sqrt(Math.pow(self.x - pond.x, 2) + Math.pow(self.y - pond.y, 2));
if (distanceToPond < 100) {
// Dog is in or near pond
dogSpeed *= 0.4; // Reduce speed significantly in water
break;
}
}
self.x += Math.cos(angle) * dogSpeed;
self.y += Math.sin(angle) * dogSpeed;
// Flash footprint when following it
if (LK.ticks % 60 === 0) {
LK.effects.flashObject(nearestFootprint, 0xFFFF00, 200);
}
// When close to footprint, search for nearby hidden animals
if (nearestFootprintDistance < 30) {
for (var a = 0; a < animals.length; a++) {
var animal = animals[a];
if (!animal.isAlive || animal.isDiscoveredByDog) {
continue;
}
var animalToFootprintDistance = Math.sqrt(Math.pow(animal.x - nearestFootprint.x, 2) + Math.pow(animal.y - nearestFootprint.y, 2));
var animalToDogDistance = Math.sqrt(Math.pow(animal.x - self.x, 2) + Math.pow(animal.y - self.y, 2));
// If animal is near this footprint and dog is close, discover it
if (animalToFootprintDistance < 100 && animalToDogDistance < self.detectionRadius * 1.2) {
animal.isDiscoveredByDog = true;
animal.graphics.visible = true;
animal.visibilityTimer = 0;
animal.isFadingOut = false;
animal.graphics.alpha = 1.0;
// Special effect for footprint-based discovery
LK.effects.flashObject(animal, 0x00FFFF, 1000); // Cyan flash for trail discovery
LK.effects.flashObject(nearestFootprint, 0x00FFFF, 500);
// Play bark sound for discovery
if (LK.ticks % 30 === 0) {
LK.getSound('bark').play();
}
// Point dog towards discovered animal
var discoveryAngle = Math.atan2(animal.y - self.y, animal.x - self.x);
self.graphics.rotation = discoveryAngle;
break;
}
}
}
// Consume energy while tracking
if (LK.ticks % 25 === 0) {
self.energy = Math.max(0, self.energy - 0.2);
}
} else if (distanceToHunter > self.followDistance) {
// Move towards hunter
var angle = Math.atan2(hunter.y - self.y, hunter.x - self.x);
// Check if dog is in pond and reduce speed
var dogFollowSpeed = self.speed;
for (var p = 0; p < ponds.length; p++) {
var pond = ponds[p];
var distanceToPond = Math.sqrt(Math.pow(self.x - pond.x, 2) + Math.pow(self.y - pond.y, 2));
if (distanceToPond < 100) {
// Dog is in or near pond
dogFollowSpeed *= 0.4; // Reduce speed significantly in water
break;
}
}
self.x += Math.cos(angle) * dogFollowSpeed;
self.y += Math.sin(angle) * dogFollowSpeed;
// Consume energy while moving
if (LK.ticks % 20 === 0) {
self.energy = Math.max(0, self.energy - 0.3);
}
}
}
// Apply breed-specific abilities
var detectionBonus = 1.0;
var speedBonus = 1.0;
var energyDrain = 1.0;
if (self.specialAbility === 'sight') {
// Pointer - better long distance detection
detectionBonus = 1.3;
} else if (self.specialAbility === 'stealth') {
// Setter - animals don't flee as quickly
speedBonus = 1.1;
} else if (self.specialAbility === 'endurance') {
// Husky - less energy drain
energyDrain = 0.7;
} else if (self.specialAbility === 'tracking') {
// Retriever - better footprint following
detectionBonus = 1.1;
}
// Scan for animals and ducks and make them visible when detected
self.alertCooldown = Math.max(0, self.alertCooldown - 1);
var allAnimals = animals.concat(ducks);
for (var i = 0; i < allAnimals.length; i++) {
var animal = allAnimals[i];
if (!animal.isAlive) {
continue;
}
var distance = Math.sqrt(Math.pow(self.x - animal.x, 2) + Math.pow(self.y - animal.y, 2));
// Make animal visible if dog detects it (with breed bonus)
var effectiveDetectionRadius = self.detectionRadius * detectionBonus;
if (distance < effectiveDetectionRadius && !animal.isDiscoveredByDog) {
animal.isDiscoveredByDog = true;
animal.graphics.visible = true;
animal.visibilityTimer = 0; // Reset visibility timer
animal.isFadingOut = false; // Reset fading flag
animal.graphics.alpha = 1.0; // Ensure full visibility
// Set as persistent target only if dog doesn't have one, prioritizing closer animals
if (!self.persistentTarget || !self.persistentTarget.isAlive) {
self.persistentTarget = animal;
// Flash the animal when first discovered and set as target
LK.effects.flashObject(animal, 0x00FF00, 800);
} else {
// Check if this animal is closer than current persistent target
var currentTargetDistance = Math.sqrt(Math.pow(self.x - self.persistentTarget.x, 2) + Math.pow(self.y - self.persistentTarget.y, 2));
if (distance < currentTargetDistance) {
// Switch to closer target
self.persistentTarget = animal;
// Flash the new priority target
LK.effects.flashObject(animal, 0x00FF00, 800);
} else {
// Flash discovered but not prioritized animal differently
LK.effects.flashObject(animal, 0xFFFF00, 400);
}
}
}
// Update last known position of persistent target
if (self.persistentTarget && self.persistentTarget.isAlive) {
self.lastKnownTargetX = self.persistentTarget.x;
self.lastKnownTargetY = self.persistentTarget.y;
}
}
// Alert to closest animal
if (self.alertCooldown === 0) {
var closestAnimal = null;
var closestDistance = Infinity;
var allAnimals = animals.concat(ducks);
for (var i = 0; i < allAnimals.length; i++) {
var animal = allAnimals[i];
if (!animal.isAlive || !animal.isDiscoveredByDog) {
continue;
}
var distance = Math.sqrt(Math.pow(self.x - animal.x, 2) + Math.pow(self.y - animal.y, 2));
if (distance < self.detectionRadius && distance < closestDistance) {
closestAnimal = animal;
closestDistance = distance;
}
}
if (closestAnimal && closestAnimal !== self.targetAnimal) {
self.targetAnimal = closestAnimal;
self.alertToAnimal();
self.alertCooldown = 180; // 3 seconds cooldown
}
}
// Regenerate energy when not moving much
var movement = Math.sqrt(Math.pow(self.x - self.lastX, 2) + Math.pow(self.y - self.lastY, 2));
if (movement < 1 && self.energy < 100) {
self.energy += 0.15;
}
// Dog whines when energy is very low
if (self.energy < 20 && LK.ticks % 180 === 0) {
LK.getSound('dog_whine').play();
}
// Walking animation logic
var currentlyWalking = movement > 0.5; // Dog is walking if movement is significant
// Start walking animation when movement begins
if (!self.isWalking && currentlyWalking) {
self.isWalking = true;
self.startWalkAnimation();
}
// Stop walking animation when movement stops
else if (self.isWalking && !currentlyWalking) {
self.isWalking = false;
self.stopWalkAnimation();
}
self.lastX = self.x;
self.lastY = self.y;
};
self.alertToAnimal = function () {
// Visual alert - flash the dog
LK.effects.flashObject(self, 0xFFFF00, 500);
// Point towards the animal
if (self.targetAnimal) {
var angle = Math.atan2(self.targetAnimal.y - self.y, self.targetAnimal.x - self.x);
self.graphics.rotation = angle;
// Play bark sound
LK.getSound('bark').play();
// Create tracking bonus for player - larger bonus if found via footprints
var trackingBonus = 5;
// Check if there are recent footprints nearby (indicating trail-based discovery)
for (var f = 0; f < footprints.length; f++) {
var footprint = footprints[f];
var footprintToAnimalDistance = Math.sqrt(Math.pow(self.targetAnimal.x - footprint.x, 2) + Math.pow(self.targetAnimal.y - footprint.y, 2));
if (footprintToAnimalDistance < 80) {
trackingBonus = 10; // Double bonus for footprint-based discovery
break;
}
}
hunter.tracking = Math.min(100, hunter.tracking + trackingBonus);
}
};
self.rest = function () {
// Dog rests and recovers energy faster
self.energy = Math.min(100, self.energy + 2);
};
self.startWalkAnimation = function () {
// Create a continuous bobbing animation while walking
self.walkAnimationLoop();
};
self.stopWalkAnimation = function () {
// Stop the walking animation and return to normal position
tween.stop(self.graphics, {
y: true,
scaleY: true
});
tween(self.graphics, {
y: 0,
scaleY: 1.0
}, {
duration: 200,
easing: tween.easeOut
});
};
self.walkAnimationLoop = function () {
if (!self.isWalking) {
return;
} // Stop if no longer walking
// Create bobbing motion with slight scale changes to simulate walking
tween(self.graphics, {
y: -3,
scaleY: 0.95
}, {
duration: 300,
easing: tween.easeInOut,
onFinish: function onFinish() {
if (self.isWalking) {
// Bounce back down
tween(self.graphics, {
y: 0,
scaleY: 1.05
}, {
duration: 300,
easing: tween.easeInOut,
onFinish: function onFinish() {
// Continue the loop if still walking
if (self.isWalking) {
self.walkAnimationLoop();
}
}
});
}
}
});
};
self.switchBreed = function (breedData) {
self.breed = breedData.name;
self.speed = breedData.speed;
self.detectionRadius = breedData.detection;
self.maxEnergy = breedData.energy;
self.energy = Math.min(self.energy, self.maxEnergy);
self.specialAbility = breedData.specialAbility;
// Apply breed color tint
self.graphics.tint = breedData.color;
// Flash effect for breed change
LK.effects.flashObject(self, 0x00FF00, 1000);
};
return self;
});
var Shop = Container.expand(function () {
var self = Container.call(this);
self.isOpen = false;
// Shop background panel
self.background = self.addChild(LK.getAsset('shop_panel', {
anchorX: 0.5,
anchorY: 0.5
}));
self.background.scaleX = 1.2;
self.background.scaleY = 1.5;
// Shop title
self.title = self.addChild(new Text2('HUNTING SHOP', {
size: 32,
fill: 0xFFFFFF
}));
self.title.anchor.set(0.5, 0);
self.title.y = -450;
// Close button
self.closeButton = self.addChild(LK.getAsset('buy_button', {
anchorX: 0.5,
anchorY: 0.5
}));
self.closeButton.y = 420;
self.closeButton.tint = 0xFF4444;
self.closeText = self.addChild(new Text2('CLOSE', {
size: 18,
fill: 0xFFFFFF
}));
self.closeText.anchor.set(0.5, 0.5);
self.closeText.y = 420;
// Shop items
self.items = [];
self.itemsContainer = self.addChild(new Container());
self.itemsContainer.y = -350;
self.open = function () {
self.isOpen = true;
self.visible = true;
self.setupItems();
// Bring shop to front
if (self.parent) {
self.parent.removeChild(self);
self.parent.addChild(self);
}
};
self.close = function () {
self.isOpen = false;
self.visible = false;
};
self.setupItems = function () {
// Clear existing items
for (var i = 0; i < self.items.length; i++) {
if (self.items[i].parent) {
self.items[i].destroy();
}
}
self.items = [];
// Add shop items
var yOffset = 0;
for (var i = 0; i < shopItemsData.length; i++) {
var item = self.itemsContainer.addChild(new ShopItem(shopItemsData[i]));
item.y = yOffset;
item.x = -200; // Center the items
self.items.push(item);
yOffset += 100;
}
};
self.down = function (x, y, obj) {
if (!self.isOpen) {
return;
}
// Check close button
if (x >= -60 && x <= 60 && y >= 400 && y <= 440) {
LK.getSound('button_click').play();
self.close();
}
};
// Initially hidden
self.visible = false;
return self;
});
var ShopItem = Container.expand(function (itemData) {
var self = Container.call(this);
self.itemData = itemData;
self.name = itemData.name;
self.price = itemData.price;
self.description = itemData.description;
self.type = itemData.type;
self.effect = itemData.effect;
// Create item display
self.panel = self.addChild(LK.getAsset('shop_panel', {
anchorX: 0,
anchorY: 0
}));
self.panel.scaleX = 0.8;
self.panel.scaleY = 0.3;
// Item name
self.nameText = self.addChild(new Text2(self.name, {
size: 24,
fill: 0xFFFFFF
}));
self.nameText.anchor.set(0, 0);
self.nameText.x = 10;
self.nameText.y = 10;
// Item price
self.priceText = self.addChild(new Text2('$' + self.price, {
size: 20,
fill: 0xFFD700
}));
self.priceText.anchor.set(0, 0);
self.priceText.x = 10;
self.priceText.y = 35;
// Description
self.descText = self.addChild(new Text2(self.description, {
size: 16,
fill: 0xCCCCCC
}));
self.descText.anchor.set(0, 0);
self.descText.x = 10;
self.descText.y = 60;
// Buy button
self.buyButton = self.addChild(LK.getAsset('buy_button', {
anchorX: 0,
anchorY: 0
}));
self.buyButton.x = 250;
self.buyButton.y = 30;
self.buyText = self.addChild(new Text2('BUY', {
size: 18,
fill: 0xFFFFFF
}));
self.buyText.anchor.set(0.5, 0.5);
self.buyText.x = 310;
self.buyText.y = 50;
self.down = function (x, y, obj) {
// Check if click is on buy button
if (x >= 250 && x <= 370 && y >= 30 && y <= 70) {
LK.getSound('button_click').play();
if (playerCoins >= self.price) {
playerCoins -= self.price;
applyItemEffect(self);
coinText.setText('Coins: ' + playerCoins);
// Visual feedback
LK.effects.flashObject(self.buyButton, 0x00FF00, 300);
LK.getSound('coin_pickup').play();
} else {
// Not enough coins
LK.effects.flashObject(self.buyButton, 0xFF0000, 300);
}
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x2F4F2F // Dark forest green
});
/****
* Game Code
****/
// Game variables
// Hunter and equipment
// Animals
// Environment
// UI elements
// Sounds
var hunter;
var huntingDog;
var animals = [];
var trees = [];
var footprints = [];
var ponds = [];
var ducks = [];
var gameScore = 0;
var dayTime = 0; // 0-1440 (24 hours in minutes)
var isAiming = false;
// Shop system variables
var playerCoins = 100; // Starting coins
var shop;
var shopButton;
var coinText;
// Map system variables
var currentMap = 0; // 0=Forest, 1=Desert, 2=Arctic
var mapButton;
var mapText;
// Season system variables
var currentSeason = storage.currentSeason || 0; // 0=Spring, 1=Summer, 2=Autumn, 3=Winter
var seasonDuration = 3600; // 60 seconds per season at 60fps
var seasonTimer = storage.seasonTimer || 0;
var seasonButton;
var seasonText;
// Dog breed data for different seasons
var dogBreeds = [{
name: 'Retriever',
season: 'Spring',
speed: 2.5,
detection: 200,
energy: 100,
specialAbility: 'tracking',
color: 0xD2691E
}, {
name: 'Pointer',
season: 'Summer',
speed: 3.0,
detection: 250,
energy: 120,
specialAbility: 'sight',
color: 0xFFD700
}, {
name: 'Setter',
season: 'Autumn',
speed: 2.8,
detection: 180,
energy: 110,
specialAbility: 'stealth',
color: 0x8B4513
}, {
name: 'Husky',
season: 'Winter',
speed: 2.2,
detection: 220,
energy: 140,
specialAbility: 'endurance',
color: 0x708090
}];
var seasons = [{
name: 'Spring',
backgroundColor: 0x228B22,
visibility: 1.0,
animalSpawnRate: 1.2,
weatherEffect: 'rain'
}, {
name: 'Summer',
backgroundColor: 0x32CD32,
visibility: 0.8,
animalSpawnRate: 0.8,
weatherEffect: 'heat'
}, {
name: 'Autumn',
backgroundColor: 0xD2691E,
visibility: 0.9,
animalSpawnRate: 1.1,
weatherEffect: 'wind'
}, {
name: 'Winter',
backgroundColor: 0xF0F8FF,
visibility: 0.6,
animalSpawnRate: 0.7,
weatherEffect: 'snow'
}];
var maps = [{
name: 'Forest',
backgroundColor: 0x2F4F2F,
animals: ['deer', 'rabbit', 'boar', 'wolf'],
treeCount: 30,
bushCount: 50,
animalCount: 15,
pondCount: 3,
duckCount: 6,
visibility: 1.0
}, {
name: 'Desert',
backgroundColor: 0x8B7355,
animals: ['rabbit', 'boar'],
treeCount: 5,
bushCount: 20,
animalCount: 8,
pondCount: 1,
duckCount: 2,
visibility: 0.7
}, {
name: 'Arctic',
backgroundColor: 0xE6F3FF,
animals: ['deer', 'wolf'],
treeCount: 15,
bushCount: 10,
animalCount: 6,
pondCount: 2,
duckCount: 4,
visibility: 0.5
}];
// Shop items data
var shopItemsData = [{
name: 'Better Rifle',
price: 150,
description: 'Increases accuracy by 20%',
type: 'weapon',
effect: 'accuracy'
}, {
name: 'Camouflage Gear',
price: 100,
description: 'Improves stealth by 25%',
type: 'equipment',
effect: 'stealth'
}, {
name: 'Tracking Boots',
price: 80,
description: 'Better tracking skills +15%',
type: 'equipment',
effect: 'tracking'
}, {
name: 'Energy Drink',
price: 50,
description: 'Restores stamina to full',
type: 'consumable',
effect: 'stamina'
}, {
name: 'Ammo Pack',
price: 40,
description: 'Restores ammo to full',
type: 'consumable',
effect: 'ammo'
}, {
name: 'Food Rations',
price: 30,
description: 'Restores hunger to full',
type: 'consumable',
effect: 'hunger'
}];
// UI elements
var ammoBar, hungerBar, scoreText, timeText;
// Initialize hunter
hunter = game.addChild(new Hunter());
hunter.x = 1024;
hunter.y = 1366;
// Initialize hunting dog
huntingDog = game.addChild(new HuntingDog());
huntingDog.x = hunter.x + 50;
huntingDog.y = hunter.y + 30;
// Set initial dog breed based on current season
var initialBreed = dogBreeds[currentSeason];
huntingDog.switchBreed(initialBreed);
// Initialize first map
createMapEnvironment(maps[currentMap]);
// Create UI
var ammoBar = LK.getAsset('ammo_bar', {
anchorX: 0,
anchorY: 0
});
LK.gui.topLeft.addChild(ammoBar);
ammoBar.x = 120;
ammoBar.y = 20;
// Ammo label
var ammoText = new Text2('Ammo', {
size: 16,
fill: 0xFFFFFF
});
ammoText.anchor.set(0, 0);
LK.gui.topLeft.addChild(ammoText);
ammoText.x = 120;
ammoText.y = 5;
hungerBar = LK.getAsset('hunger_bar', {
anchorX: 0,
anchorY: 0
});
LK.gui.topLeft.addChild(hungerBar);
hungerBar.x = 120;
hungerBar.y = 50;
// Dog energy bar
var dogEnergyBar = LK.getAsset('stamina_bar', {
anchorX: 0,
anchorY: 0
});
LK.gui.topLeft.addChild(dogEnergyBar);
dogEnergyBar.x = 120;
dogEnergyBar.y = 80;
dogEnergyBar.tint = 0xFFD700; // Golden color for dog
// Dog energy label
var dogEnergyText = new Text2('Dog Energy', {
size: 16,
fill: 0xFFFFFF
});
dogEnergyText.anchor.set(0, 0);
LK.gui.topLeft.addChild(dogEnergyText);
dogEnergyText.x = 120;
dogEnergyText.y = 105;
scoreText = new Text2('Score: 0', {
size: 40,
fill: 0xFFFFFF
});
scoreText.anchor.set(0, 0);
LK.gui.topRight.addChild(scoreText);
scoreText.x = -200;
scoreText.y = 20;
timeText = new Text2('Dawn', {
size: 35,
fill: 0xFFD700
});
timeText.anchor.set(0.5, 0);
LK.gui.top.addChild(timeText);
timeText.y = 20;
// Coin display
coinText = new Text2('Coins: ' + playerCoins, {
size: 40,
fill: 0xFFD700
});
coinText.anchor.set(1, 0);
LK.gui.topRight.addChild(coinText);
coinText.x = -10;
coinText.y = 60;
// Shop button
shopButton = LK.getAsset('shop_button', {
anchorX: 0.5,
anchorY: 0.5
});
LK.gui.bottomRight.addChild(shopButton);
shopButton.x = -100;
shopButton.y = -50;
var shopButtonText = new Text2('SHOP', {
size: 24,
fill: 0xFFFFFF
});
shopButtonText.anchor.set(0.5, 0.5);
LK.gui.bottomRight.addChild(shopButtonText);
shopButtonText.x = -100;
shopButtonText.y = -50;
// Initialize shop
shop = game.addChild(new Shop());
shop.x = 1024;
shop.y = 1366;
shop.visible = false;
// Map button
mapButton = LK.getAsset('shop_button', {
anchorX: 0.5,
anchorY: 0.5
});
LK.gui.bottomLeft.addChild(mapButton);
mapButton.x = 100;
mapButton.y = -50;
mapText = new Text2('MAP: ' + maps[currentMap].name, {
size: 20,
fill: 0xFFFFFF
});
mapText.anchor.set(0.5, 0.5);
LK.gui.bottomLeft.addChild(mapText);
mapText.x = 100;
mapText.y = -50;
// Season button
seasonButton = LK.getAsset('shop_button', {
anchorX: 0.5,
anchorY: 0.5
});
LK.gui.bottomLeft.addChild(seasonButton);
seasonButton.x = 100;
seasonButton.y = -120;
seasonText = new Text2('SEASON: ' + seasons[currentSeason].name, {
size: 18,
fill: 0xFFFFFF
});
seasonText.anchor.set(0.5, 0.5);
LK.gui.bottomLeft.addChild(seasonText);
seasonText.x = 100;
seasonText.y = -120;
// Create footprint function
function createFootprint(x, y) {
var footprint = game.addChild(new Footprint());
footprint.x = x;
footprint.y = y;
footprints.push(footprint);
}
// Function to switch seasons
function switchToSeason(seasonIndex) {
currentSeason = seasonIndex;
var seasonData = seasons[currentSeason];
var breedData = dogBreeds[currentSeason];
// Update background color to season theme
var mapData = maps[currentMap];
var seasonColor = seasonData.backgroundColor;
game.setBackgroundColor(seasonColor);
// Switch dog breed
if (huntingDog) {
huntingDog.switchBreed(breedData);
}
// Update UI
seasonText.setText('SEASON: ' + seasonData.name);
// Save to storage
storage.currentSeason = currentSeason;
storage.seasonTimer = 0;
seasonTimer = 0;
// Visual effects for season change
LK.effects.flashScreen(seasonColor, 1500);
}
// Function to switch maps
function switchToMap(mapIndex) {
currentMap = mapIndex;
var mapData = maps[currentMap];
// Apply current season's background color
var seasonData = seasons[currentSeason];
game.setBackgroundColor(seasonData.backgroundColor);
// Clear existing environment
for (var i = trees.length - 1; i >= 0; i--) {
if (trees[i].parent) {
trees[i].destroy();
}
}
trees = [];
// Clear existing animals
for (var i = animals.length - 1; i >= 0; i--) {
if (animals[i].parent) {
animals[i].destroy();
}
}
animals = [];
// Clear footprints
for (var i = footprints.length - 1; i >= 0; i--) {
if (footprints[i].parent) {
footprints[i].destroy();
}
}
footprints = [];
// Clear existing ponds
for (var i = ponds.length - 1; i >= 0; i--) {
if (ponds[i].parent) {
ponds[i].destroy();
}
}
ponds = [];
// Clear existing ducks
for (var i = ducks.length - 1; i >= 0; i--) {
if (ducks[i].parent) {
ducks[i].destroy();
}
}
ducks = [];
// Create new environment
createMapEnvironment(mapData);
// Update UI
mapText.setText('MAP: ' + mapData.name);
// Reset hunter position
hunter.x = 1024;
hunter.y = 1366;
// Reset dog position near hunter
if (huntingDog) {
huntingDog.x = hunter.x + 50;
huntingDog.y = hunter.y + 30;
huntingDog.energy = 100;
huntingDog.targetAnimal = null;
}
}
// Function to create environment for current map
function createMapEnvironment(mapData) {
// Create trees
for (var i = 0; i < mapData.treeCount; i++) {
var tree = game.addChild(LK.getAsset('tree', {
anchorX: 0.5,
anchorY: 1.0
}));
tree.x = Math.random() * 1948 + 50;
tree.y = Math.random() * 2582 + 100;
// Apply map-specific tinting
if (currentMap === 1) {
// Desert
tree.tint = 0xD2B48C; // Sandy brown
} else if (currentMap === 2) {
// Arctic
tree.tint = 0xC0C0C0; // Silver/white
}
trees.push(tree);
}
// Add bushes
for (var i = 0; i < mapData.bushCount; i++) {
var bush = game.addChild(LK.getAsset('bush', {
anchorX: 0.5,
anchorY: 0.5
}));
bush.x = Math.random() * 1948 + 50;
bush.y = Math.random() * 2582 + 100;
// Apply map-specific tinting
if (currentMap === 1) {
// Desert
bush.tint = 0xDAA520; // Golden rod
} else if (currentMap === 2) {
// Arctic
bush.tint = 0xF0F8FF; // Alice blue
}
}
// Spawn animals for this map
for (var i = 0; i < mapData.animalCount; i++) {
var animalType = mapData.animals[Math.floor(Math.random() * mapData.animals.length)];
var animal = game.addChild(new Animal(animalType));
animal.x = Math.random() * 1848 + 100;
animal.y = Math.random() * 2482 + 100;
// Reset visibility for new map
animal.isDiscoveredByDog = false;
animal.graphics.visible = false;
animal.graphics.alpha = 1.0; // Reset transparency
animal.visibilityTimer = 0; // Reset visibility timer
animal.isFadingOut = false; // Reset fading flag
animals.push(animal);
}
// Create ponds
for (var i = 0; i < mapData.pondCount; i++) {
var pond = game.addChild(LK.getAsset('pond', {
anchorX: 0.5,
anchorY: 0.5
}));
pond.x = Math.random() * 1648 + 200; // Keep ponds away from edges
pond.y = Math.random() * 2382 + 200;
// Apply map-specific tinting
if (currentMap === 1) {
// Desert - more muddy water
pond.tint = 0x8FBC8F;
} else if (currentMap === 2) {
// Arctic - icy water
pond.tint = 0xAFEEEE;
}
ponds.push(pond);
}
// Spawn ducks in ponds
var ducksPerPond = Math.floor(mapData.duckCount / mapData.pondCount);
var extraDucks = mapData.duckCount % mapData.pondCount;
for (var i = 0; i < mapData.pondCount; i++) {
var pond = ponds[i];
var ducksForThisPond = ducksPerPond + (i < extraDucks ? 1 : 0);
for (var j = 0; j < ducksForThisPond; j++) {
var duck = game.addChild(new Duck());
// Position duck near pond center with some randomness
duck.x = pond.x + (Math.random() - 0.5) * 60;
duck.y = pond.y + (Math.random() - 0.5) * 60;
duck.pondX = pond.x;
duck.pondY = pond.y;
// Start ducks as invisible until dog finds them
duck.isDiscoveredByDog = false;
duck.graphics.visible = false;
duck.graphics.alpha = 1.0;
duck.visibilityTimer = 0;
duck.isFadingOut = false;
ducks.push(duck);
}
}
}
// Shop item effects function
function applyItemEffect(shopItem) {
var effect = shopItem.effect;
var type = shopItem.type;
if (type === 'consumable') {
if (effect === 'ammo') {
hunter.ammo = hunter.maxAmmo;
LK.getSound('reload').play();
} else if (effect === 'hunger') {
hunter.hunger = 100;
}
} else if (type === 'equipment' || type === 'weapon') {
if (effect === 'accuracy') {
hunter.accuracy = Math.min(100, hunter.accuracy + 20);
} else if (effect === 'stealth') {
hunter.stealth = Math.min(100, hunter.stealth + 25);
} else if (effect === 'tracking') {
hunter.tracking = Math.min(100, hunter.tracking + 15);
}
}
}
// Function to award coins for hunting
function awardCoins(animalType) {
var coinReward = 5;
if (animalType === 'deer') {
coinReward = 10;
} else if (animalType === 'boar') {
coinReward = 15;
} else if (animalType === 'wolf') {
coinReward = 25;
} else if (animalType === 'duck') {
coinReward = 12;
}
playerCoins += coinReward;
coinText.setText('Coins: ' + playerCoins);
// Create coin visual effect
var coinEffect = game.addChild(LK.getAsset('coin', {
anchorX: 0.5,
anchorY: 0.5
}));
coinEffect.x = hunter.x;
coinEffect.y = hunter.y - 50;
// Animate coin moving up and fading
tween(coinEffect, {
y: coinEffect.y - 100,
alpha: 0
}, {
duration: 1000,
onFinish: function onFinish() {
if (coinEffect.parent) {
coinEffect.destroy();
}
}
});
}
// Shooting function
function shootAtTarget(targetX, targetY) {
if (hunter.ammo <= 0) {
return;
}
// hunter.ammo -= 1; // Infinite ammo - commented out ammo consumption
LK.getSound('gunshot').play();
// Check if shot is blocked by trees
var shotBlocked = false;
for (var t = 0; t < trees.length; t++) {
var tree = trees[t];
// Calculate if the shot line intersects with the tree
var dx = targetX - hunter.x;
var dy = targetY - hunter.y;
var shotLength = Math.sqrt(dx * dx + dy * dy);
// Normalize the shot direction
var shotDirX = dx / shotLength;
var shotDirY = dy / shotLength;
// Check multiple points along the shot path
for (var step = 0; step < shotLength; step += 10) {
var checkX = hunter.x + shotDirX * step;
var checkY = hunter.y + shotDirY * step;
var treeDistance = Math.sqrt(Math.pow(checkX - tree.x, 2) + Math.pow(checkY - tree.y, 2));
if (treeDistance < 50) {
// Tree collision radius
shotBlocked = true;
break;
}
}
if (shotBlocked) {
break;
}
}
// If shot is blocked, don't check for animal hits
if (shotBlocked) {
return;
}
// Check if we hit any animals or ducks
var allTargets = animals.concat(ducks);
for (var i = allTargets.length - 1; i >= 0; i--) {
var animal = allTargets[i];
var distance = Math.sqrt(Math.pow(animal.x - targetX, 2) + Math.pow(animal.y - targetY, 2));
if (distance < 60) {
// Hit!
animal.isAlive = false;
animal.graphics.alpha = 0.5;
LK.getSound('animal_hurt').play();
// Clear dog's persistent target if this animal was being chased
if (huntingDog && huntingDog.persistentTarget === animal) {
huntingDog.persistentTarget = null;
huntingDog.wasChasing = false;
// Flash dog to show successful hunt completion
LK.effects.flashObject(huntingDog, 0x00FF00, 500);
}
// Award points based on animal type
var points = 10;
if (animal.animalType === 'deer') {
points = 20;
} else if (animal.animalType === 'boar') {
points = 30;
} else if (animal.animalType === 'wolf') {
points = 50;
} else {
points = 15;
} // Duck points
gameScore += points;
LK.setScore(gameScore);
scoreText.setText('Score: ' + gameScore);
// Award coins
if (animal.animalType) {
awardCoins(animal.animalType);
} else {
awardCoins('duck'); // For ducks
}
// Flash effect
LK.effects.flashObject(animal, 0xff0000, 500);
// Remove animal after delay
LK.setTimeout(function () {
if (animal.parent) {
animal.destroy();
// Remove from appropriate array
if (animal.animalType) {
for (var j = animals.length - 1; j >= 0; j--) {
if (animals[j] === animal) {
animals.splice(j, 1);
break;
}
}
} else {
for (var j = ducks.length - 1; j >= 0; j--) {
if (ducks[j] === animal) {
ducks.splice(j, 1);
break;
}
}
}
}
}, 1000);
break;
}
}
}
// Movement handling
var targetX = hunter.x;
var targetY = hunter.y;
var isMovingToTarget = false;
function handleMove(x, y, obj) {
targetX = x;
targetY = y;
isMovingToTarget = true;
}
game.move = handleMove;
game.down = function (x, y, obj) {
// Convert to GUI coordinates for shop button check - with null check
if (obj && obj.parent && obj.position) {
var guiPos = LK.gui.bottomRight.toLocal(obj.parent.toGlobal(obj.position));
// Check if shop button was clicked
if (guiPos.x >= -175 && guiPos.x <= -25 && guiPos.y >= -75 && guiPos.y <= -25) {
console.log("Shop button clicked!");
LK.getSound('button_click').play();
if (!shop.isOpen) {
shop.open();
console.log("Shop opened, visible:", shop.visible);
}
return;
}
// Check if map button was clicked
var mapGuiPos = LK.gui.bottomLeft.toLocal(obj.parent.toGlobal(obj.position));
if (mapGuiPos.x >= 25 && mapGuiPos.x <= 175 && mapGuiPos.y >= -75 && mapGuiPos.y <= -25) {
console.log("Map button clicked!");
LK.getSound('button_click').play();
currentMap = (currentMap + 1) % maps.length;
switchToMap(currentMap);
LK.effects.flashObject(mapButton, 0x00FF00, 300);
return;
}
// Check if season button was clicked
if (mapGuiPos.x >= 25 && mapGuiPos.x <= 175 && mapGuiPos.y >= -145 && mapGuiPos.y <= -95) {
console.log("Season button clicked!");
LK.getSound('button_click').play();
currentSeason = (currentSeason + 1) % seasons.length;
switchToSeason(currentSeason);
LK.effects.flashObject(seasonButton, 0x00FF00, 300);
return;
}
}
// Check if clicking on the dog to give it rest command
var dogDistance = Math.sqrt(Math.pow(huntingDog.x - x, 2) + Math.pow(huntingDog.y - y, 2));
if (dogDistance < 60) {
huntingDog.rest();
LK.effects.flashObject(huntingDog, 0x00FF00, 300);
return;
}
if (isAiming) {
shootAtTarget(x, y);
isAiming = false;
} else {
isAiming = true;
LK.setTimeout(function () {
isAiming = false;
}, 2000);
}
};
// Main game update loop
game.update = function () {
// Update season progression
seasonTimer++;
storage.seasonTimer = seasonTimer;
// Automatic season change every seasonDuration ticks
if (seasonTimer >= seasonDuration) {
var nextSeason = (currentSeason + 1) % seasons.length;
switchToSeason(nextSeason);
}
// Update day/night cycle
dayTime += 0.5; // 0.5 minutes per frame at 60fps = 48 minutes real time for full day
if (dayTime >= 1440) {
dayTime = 0;
}
// Update time display
var hour = Math.floor(dayTime / 60);
var timeOfDay = '';
if (hour >= 5 && hour < 12) {
timeOfDay = 'Morning';
} else if (hour >= 12 && hour < 18) {
timeOfDay = 'Afternoon';
} else if (hour >= 18 && hour < 21) {
timeOfDay = 'Evening';
} else {
timeOfDay = 'Night';
}
timeText.setText(timeOfDay + ' (' + hour + ':' + Math.floor(dayTime % 60).toString().padStart(2, '0') + ')');
// Move hunter towards target
if (isMovingToTarget) {
var dx = targetX - hunter.x;
var dy = targetY - hunter.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 5) {
var moveSpeed = 3;
var newX = hunter.x + dx / distance * moveSpeed;
var newY = hunter.y + dy / distance * moveSpeed;
// Check if new position would be inside any pond
var canMove = true;
for (var p = 0; p < ponds.length; p++) {
var pond = ponds[p];
var distanceToPond = Math.sqrt(Math.pow(newX - pond.x, 2) + Math.pow(newY - pond.y, 2));
if (distanceToPond < 100) {
// Pond collision radius
canMove = false;
break;
}
}
// Only move if not colliding with pond
if (canMove) {
hunter.x = newX;
hunter.y = newY;
// Consume hunger while moving
if (LK.ticks % 10 === 0) {
hunter.hunger = Math.max(0, hunter.hunger - 0.1);
}
// Play footstep sound occasionally
if (LK.ticks % 30 === 0) {
LK.getSound('footstep').play();
}
} else {
// Stop moving if hitting pond
isMovingToTarget = false;
}
} else {
isMovingToTarget = false;
}
}
// Update UI bars
ammoBar.scaleX = hunter.ammo / hunter.maxAmmo;
hungerBar.scaleX = hunter.hunger / 100;
dogEnergyBar.scaleX = huntingDog.energy / 100;
// Update hunting dog
if (huntingDog) {
huntingDog.update();
}
// Update animals
for (var i = 0; i < animals.length; i++) {
animals[i].update();
}
// Update ducks
for (var i = 0; i < ducks.length; i++) {
ducks[i].update();
}
// Update footprints
for (var i = 0; i < footprints.length; i++) {
footprints[i].update();
}
// Spawn new animals occasionally (affected by season)
var mapData = maps[currentMap];
var seasonData = seasons[currentSeason];
var adjustedAnimalCount = Math.floor(mapData.animalCount * seasonData.animalSpawnRate);
var spawnRate = Math.floor(600 / seasonData.animalSpawnRate);
if (animals.length < adjustedAnimalCount && LK.ticks % spawnRate === 0) {
var animalType = mapData.animals[Math.floor(Math.random() * mapData.animals.length)];
var animal = game.addChild(new Animal(animalType));
animal.x = Math.random() * 1848 + 100;
animal.y = Math.random() * 2482 + 100;
// Start new animals as invisible until dog finds them
animal.isDiscoveredByDog = false;
animal.graphics.visible = false;
animal.graphics.alpha = 1.0; // Reset transparency
animal.visibilityTimer = 0; // Reset visibility timer
animal.isFadingOut = false; // Reset fading flag
animals.push(animal);
}
// Spawn new ducks occasionally
if (ducks.length < mapData.duckCount && LK.ticks % 800 === 0) {
if (ponds.length > 0) {
var randomPond = ponds[Math.floor(Math.random() * ponds.length)];
var duck = game.addChild(new Duck());
duck.x = randomPond.x + (Math.random() - 0.5) * 60;
duck.y = randomPond.y + (Math.random() - 0.5) * 60;
duck.pondX = randomPond.x;
duck.pondY = randomPond.y;
// Start new ducks as invisible until dog finds them
duck.isDiscoveredByDog = false;
duck.graphics.visible = false;
duck.graphics.alpha = 1.0;
duck.visibilityTimer = 0;
duck.isFadingOut = false;
ducks.push(duck);
}
}
// Game over condition - if hunter runs out of hunger
if (hunter.hunger <= 0) {
LK.effects.flashScreen(0xff0000, 1000);
LK.showGameOver();
}
}; /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
var Animal = Container.expand(function (animalType) {
var self = Container.call(this);
self.animalType = animalType || 'deer';
self.graphics = self.attachAsset(self.animalType, {
anchorX: 0.5,
anchorY: 0.5
});
// Animal properties
self.health = 100;
self.alertLevel = 0;
self.speed = 0.5 + Math.random() * 1.5;
self.direction = Math.random() * Math.PI * 2;
self.isAlive = true;
self.detectionRadius = 150 + Math.random() * 100;
self.lastX = self.x;
self.lastY = self.y;
// Visibility properties for dog tracking system
self.isDiscoveredByDog = false;
self.graphics.visible = false; // Start invisible
self.visibilityTimer = 0; // Timer for how long animal has been visible
self.maxVisibilityTime = 300; // 5 seconds at 60fps before going invisible
self.isFadingOut = false; // Flag to prevent multiple fade animations
// Movement timer
self.moveTimer = 0;
self.moveChangeInterval = 60 + Math.random() * 120;
self.update = function () {
if (!self.isAlive) {
return;
}
// Random movement behavior
self.moveTimer++;
if (self.moveTimer >= self.moveChangeInterval) {
self.direction += (Math.random() - 0.5) * 0.5;
self.moveTimer = 0;
self.moveChangeInterval = 60 + Math.random() * 120;
}
// Move animal
self.x += Math.cos(self.direction) * self.speed;
self.y += Math.sin(self.direction) * self.speed;
// Keep within bounds
if (self.x < 50) {
self.x = 50;
self.direction = Math.random() * Math.PI * 2;
}
if (self.x > 1998) {
self.x = 1998;
self.direction = Math.random() * Math.PI * 2;
}
if (self.y < 50) {
self.y = 50;
self.direction = Math.random() * Math.PI * 2;
}
if (self.y > 2682) {
self.y = 2682;
self.direction = Math.random() * Math.PI * 2;
}
// Dog and player detection behavior
var distanceToDog = Math.sqrt(Math.pow(self.x - huntingDog.x, 2) + Math.pow(self.y - huntingDog.y, 2));
var distanceToPlayer = Math.sqrt(Math.pow(self.x - hunter.x, 2) + Math.pow(self.y - hunter.y, 2));
// If discovered by dog, flee from dog
if (self.isDiscoveredByDog && distanceToDog < 300) {
// Flee from dog
var fleeFromDogAngle = Math.atan2(self.y - huntingDog.y, self.x - huntingDog.x);
self.direction = fleeFromDogAngle;
self.speed = Math.min(4, self.speed + 0.15);
self.graphics.alpha = 1.0;
} else if (distanceToPlayer < self.detectionRadius) {
// Normal player detection behavior
self.alertLevel = Math.min(100, self.alertLevel + 2);
// Flee from player
var fleeAngle = Math.atan2(self.y - hunter.y, self.x - hunter.x);
self.direction = fleeAngle;
self.speed = Math.min(4, self.speed + 0.1);
self.graphics.alpha = 1.0; // Fully visible when fleeing from player
} else {
self.alertLevel = Math.max(0, self.alertLevel - 1);
self.speed = Math.max(1, self.speed - 0.05);
self.graphics.alpha = 1.0; // Fully visible when calm
}
// Handle visibility timer - make animal invisible after being seen for some time
// But don't turn invisible if being chased by the dog
var isBeingChased = distanceToDog < 300 && self.isDiscoveredByDog;
if (self.isDiscoveredByDog && self.graphics.visible && !isBeingChased) {
self.visibilityTimer++;
// Start fading out when timer reaches max visibility time
if (self.visibilityTimer >= self.maxVisibilityTime && !self.isFadingOut) {
self.isFadingOut = true;
// Use tween to fade out the animal
tween(self.graphics, {
alpha: 0
}, {
duration: 1000,
easing: tween.easeOut,
onFinish: function onFinish() {
self.graphics.visible = false;
self.isDiscoveredByDog = false;
self.visibilityTimer = 0;
self.isFadingOut = false;
self.graphics.alpha = 1.0; // Reset alpha for next discovery
}
});
}
} else if (isBeingChased) {
// Reset visibility timer while being chased to prevent fading
self.visibilityTimer = 0;
self.isFadingOut = false;
// Stop any ongoing fade animation
tween.stop(self.graphics, {
alpha: true
});
self.graphics.alpha = 1.0;
}
// Create footprints occasionally - more frequent when fleeing
var footprintChance = 0.02;
if (self.alertLevel > 50 || self.isDiscoveredByDog) {
footprintChance = 0.08; // More footprints when scared or being chased
}
if (Math.random() < footprintChance) {
createFootprint(self.x, self.y);
}
// Play rustling sound when animal is moving and alert
if (self.alertLevel > 30 && Math.random() < 0.01) {
LK.getSound('rustle').play();
}
};
return self;
});
var Duck = Container.expand(function () {
var self = Container.call(this);
self.graphics = self.attachAsset('duck', {
anchorX: 0.5,
anchorY: 0.5
});
// Duck properties
self.health = 100;
self.speed = 1.0;
self.direction = Math.random() * Math.PI * 2;
self.isAlive = true;
self.swimRadius = 80; // How far from pond center duck will swim
self.pondX = 0; // Will be set when duck is assigned to pond
self.pondY = 0;
self.isDiscoveredByDog = false;
self.graphics.visible = false;
self.visibilityTimer = 0;
self.maxVisibilityTime = 400;
self.isFadingOut = false;
self.moveTimer = 0;
self.moveChangeInterval = 90 + Math.random() * 60;
self.update = function () {
if (!self.isAlive) {
return;
}
// Duck swimming behavior - stay near pond center
self.moveTimer++;
if (self.moveTimer >= self.moveChangeInterval) {
// Change direction occasionally
self.direction += (Math.random() - 0.5) * 0.8;
self.moveTimer = 0;
self.moveChangeInterval = 90 + Math.random() * 60;
}
// Move duck
self.x += Math.cos(self.direction) * self.speed;
self.y += Math.sin(self.direction) * self.speed;
// Keep duck within pond area
var distanceFromPondCenter = Math.sqrt(Math.pow(self.x - self.pondX, 2) + Math.pow(self.y - self.pondY, 2));
if (distanceFromPondCenter > self.swimRadius) {
// Turn back toward pond center
var angleToPondCenter = Math.atan2(self.pondY - self.y, self.pondX - self.x);
self.direction = angleToPondCenter + (Math.random() - 0.5) * 0.5;
// Move back toward center
self.x += Math.cos(angleToPondCenter) * 2;
self.y += Math.sin(angleToPondCenter) * 2;
}
// Dog and player detection behavior
var distanceToDog = Math.sqrt(Math.pow(self.x - huntingDog.x, 2) + Math.pow(self.y - huntingDog.y, 2));
var distanceToPlayer = Math.sqrt(Math.pow(self.x - hunter.x, 2) + Math.pow(self.y - hunter.y, 2));
// If discovered by dog, stay more visible and swim faster
if (self.isDiscoveredByDog && distanceToDog < 200) {
self.speed = Math.min(2, self.speed + 0.1);
self.graphics.alpha = 1.0;
} else if (distanceToPlayer < 120) {
// Player detection - swim away from player
var fleeAngle = Math.atan2(self.y - hunter.y, self.x - hunter.x);
self.direction = fleeAngle;
self.speed = Math.min(2.5, self.speed + 0.1);
self.graphics.alpha = 1.0;
} else {
self.speed = Math.max(1, self.speed - 0.05);
self.graphics.alpha = 1.0;
}
// Handle visibility like other animals
var isBeingChased = distanceToDog < 200 && self.isDiscoveredByDog;
if (self.isDiscoveredByDog && self.graphics.visible && !isBeingChased) {
self.visibilityTimer++;
if (self.visibilityTimer >= self.maxVisibilityTime && !self.isFadingOut) {
self.isFadingOut = true;
tween(self.graphics, {
alpha: 0
}, {
duration: 1200,
easing: tween.easeOut,
onFinish: function onFinish() {
self.graphics.visible = false;
self.isDiscoveredByDog = false;
self.visibilityTimer = 0;
self.isFadingOut = false;
self.graphics.alpha = 1.0;
}
});
}
} else if (isBeingChased) {
self.visibilityTimer = 0;
self.isFadingOut = false;
tween.stop(self.graphics, {
alpha: true
});
self.graphics.alpha = 1.0;
}
// Create splash effects occasionally
if (Math.random() < 0.01) {
// Create small ripple effect (using footprint as placeholder)
createFootprint(self.x + (Math.random() - 0.5) * 20, self.y + (Math.random() - 0.5) * 20);
}
};
return self;
});
var Footprint = Container.expand(function () {
var self = Container.call(this);
self.graphics = self.attachAsset('footprint', {
anchorX: 0.5,
anchorY: 0.5
});
self.graphics.alpha = 0.1;
self.lifetime = 600; // 10 seconds at 60fps
self.update = function () {
self.lifetime--;
self.graphics.alpha = self.lifetime / 600 * 0.1;
if (self.lifetime <= 0) {
self.destroy();
for (var i = footprints.length - 1; i >= 0; i--) {
if (footprints[i] === self) {
footprints.splice(i, 1);
break;
}
}
}
};
return self;
});
// Game variables
var Hunter = Container.expand(function () {
var self = Container.call(this);
self.graphics = self.attachAsset('hunter', {
anchorX: 0.5,
anchorY: 0.5
});
self.rifle = self.addChild(LK.getAsset('rifle', {
anchorX: 0,
anchorY: 0.5
}));
self.rifle.x = 30;
self.rifle.y = 0;
// Hunter stats
self.ammo = 30;
self.maxAmmo = 30;
self.hunger = 100;
self.accuracy = 50;
self.stealth = 50;
self.tracking = 50;
self.isMoving = false;
self.lastMoveTime = 0;
return self;
});
var HuntingDog = Container.expand(function () {
var self = Container.call(this);
self.graphics = self.attachAsset('dog', {
anchorX: 0.5,
anchorY: 0.5
});
// Dog properties - will be updated based on breed
self.energy = 100;
self.maxEnergy = 100;
self.loyalty = 80;
self.tracking = 60;
self.speed = 2.5;
self.followDistance = 80;
self.detectionRadius = 200;
self.breed = 'Retriever';
self.specialAbility = 'tracking';
self.isFollowing = true;
self.targetAnimal = null;
self.alertCooldown = 0;
self.wasChasing = false; // Track if dog was previously chasing
self.persistentTarget = null; // Animal that dog will pursue until shot
self.lastKnownTargetX = 0; // Last known position of target
self.lastKnownTargetY = 0;
// Movement properties
self.targetX = 0;
self.targetY = 0;
self.lastX = self.x;
self.lastY = self.y;
// Walking animation properties
self.isWalking = false;
self.walkAnimationOffset = 0;
self.walkAnimationSpeed = 0.2;
self.update = function () {
if (self.energy <= 0) {
return;
}
// Single target priority system - only chase one animal at a time
var targetAnimal = null;
var targetDistance = Infinity;
// First priority: continue chasing persistent target if it exists and is alive
if (self.persistentTarget && self.persistentTarget.isAlive && self.persistentTarget.isDiscoveredByDog) {
var persistentDistance = Math.sqrt(Math.pow(self.x - self.persistentTarget.x, 2) + Math.pow(self.y - self.persistentTarget.y, 2));
if (persistentDistance < 400) {
// Extended chase range
targetAnimal = self.persistentTarget;
targetDistance = persistentDistance;
} else {
// Clear persistent target if too far away
self.persistentTarget = null;
}
}
// Second priority: if no persistent target, find closest discovered animal
if (!targetAnimal) {
var allAnimals = animals.concat(ducks);
for (var a = 0; a < allAnimals.length; a++) {
var animal = allAnimals[a];
if (!animal.isAlive || !animal.isDiscoveredByDog) {
continue;
}
var animalDistance = Math.sqrt(Math.pow(self.x - animal.x, 2) + Math.pow(self.y - animal.y, 2));
if (animalDistance < 250 && animalDistance < targetDistance) {
targetAnimal = animal;
targetDistance = animalDistance;
// Set as new persistent target
self.persistentTarget = animal;
}
}
}
// Use single target for all chase logic
var closestAnimal = targetAnimal;
var closestAnimalDistance = targetDistance;
// Chase behavior - prioritize chasing animals over following hunter
// Expanded detection range for persistent chasing
if (closestAnimal && closestAnimalDistance < 400) {
// Increased range from 250 to 400
// Set chasing flag
self.wasChasing = true;
// Stop any existing return tween
tween.stop(self, {
x: true,
y: true
});
// Strategic herding behavior to drive animal toward player
var animalToHunterAngle = Math.atan2(hunter.y - closestAnimal.y, hunter.x - closestAnimal.x);
var dogToAnimalAngle = Math.atan2(closestAnimal.y - self.y, closestAnimal.x - self.x);
var animalToHunterDistance = Math.sqrt(Math.pow(closestAnimal.x - hunter.x, 2) + Math.pow(closestAnimal.y - hunter.y, 2));
// Auto-shooting when dog is herding animal close to hunter
if (animalToHunterDistance < 250) {
// Animal is close to hunter
// Check if hunter has clear line of sight to animal
var shotBlocked = false;
var dx = closestAnimal.x - hunter.x;
var dy = closestAnimal.y - hunter.y;
var shotLength = Math.sqrt(dx * dx + dy * dy);
var shotDirX = dx / shotLength;
var shotDirY = dy / shotLength;
// Check for tree obstacles
for (var t = 0; t < trees.length; t++) {
var tree = trees[t];
for (var step = 0; step < shotLength; step += 10) {
var checkX = hunter.x + shotDirX * step;
var checkY = hunter.y + shotDirY * step;
var treeDistance = Math.sqrt(Math.pow(checkX - tree.x, 2) + Math.pow(checkY - tree.y, 2));
if (treeDistance < 50) {
shotBlocked = true;
break;
}
}
if (shotBlocked) break;
}
// Auto-shoot if clear shot and within range (every 60 ticks = 1 second)
if (!shotBlocked && shotLength < 300 && LK.ticks % 60 === 0) {
shootAtTarget(closestAnimal.x, closestAnimal.y);
}
}
// Position dog on the opposite side of animal from hunter to herd it toward player
var herdingAngle = animalToHunterAngle + Math.PI; // Opposite direction from hunter
var optimalDistance = 80; // Distance to maintain from animal while herding
// Calculate where dog should be to herd animal toward hunter
var targetHerdX = closestAnimal.x + Math.cos(herdingAngle) * optimalDistance;
var targetHerdY = closestAnimal.y + Math.sin(herdingAngle) * optimalDistance;
// Move toward herding position
var moveToHerdAngle = Math.atan2(targetHerdY - self.y, targetHerdX - self.x);
var distanceToHerdPosition = Math.sqrt(Math.pow(targetHerdX - self.x, 2) + Math.pow(targetHerdY - self.y, 2));
// If dog is far from optimal herding position, move toward it
if (distanceToHerdPosition > 30) {
// Check if dog is in pond and reduce speed
var dogHerdSpeed = self.speed * 1.5;
for (var p = 0; p < ponds.length; p++) {
var pond = ponds[p];
var distanceToPond = Math.sqrt(Math.pow(self.x - pond.x, 2) + Math.pow(self.y - pond.y, 2));
if (distanceToPond < 100) {
// Dog is in or near pond
dogHerdSpeed *= 0.4; // Reduce speed significantly in water
break;
}
}
self.x += Math.cos(moveToHerdAngle) * dogHerdSpeed;
self.y += Math.sin(moveToHerdAngle) * dogHerdSpeed;
} else {
// If in good herding position, apply pressure to drive animal toward hunter
var pressureAngle = Math.atan2(closestAnimal.y - self.y, closestAnimal.x - self.x);
// Check if dog is in pond and reduce speed
var dogChaseSpeed = self.speed * 1.0;
for (var p = 0; p < ponds.length; p++) {
var pond = ponds[p];
var distanceToPond = Math.sqrt(Math.pow(self.x - pond.x, 2) + Math.pow(self.y - pond.y, 2));
if (distanceToPond < 100) {
// Dog is in or near pond
dogChaseSpeed *= 0.4; // Reduce speed significantly in water
break;
}
}
self.x += Math.cos(pressureAngle) * dogChaseSpeed;
self.y += Math.sin(pressureAngle) * dogChaseSpeed;
}
// Point towards the animal being herded
self.graphics.rotation = dogToAnimalAngle;
// Bark more frequently when herding toward player (closer to hunter)
var barkFrequency = animalToHunterDistance < 200 ? 45 : 60; // More frequent barking
if (LK.ticks % barkFrequency === 0) {
LK.getSound('bark').play();
}
// Consume more energy while chasing
if (LK.ticks % 20 === 0) {
// Reduced energy consumption frequency from 15 to 20
self.energy = Math.max(0, self.energy - 0.3); // Reduced energy drain from 0.5 to 0.3
}
// Flash the dog to show it's in chase mode - green flash when successfully herding toward player
if (LK.ticks % 30 === 0) {
var flashColor = animalToHunterDistance < 150 ? 0x44FF44 : 0xFF4444; // Green when close to hunter, red otherwise
LK.effects.flashObject(self, flashColor, 150);
}
}
// If no animals are in immediate chase range, search for persistent target within larger radius
else if (!closestAnimal && self.persistentTarget && self.persistentTarget.isAlive) {
var searchDistance = Math.sqrt(Math.pow(self.x - self.persistentTarget.x, 2) + Math.pow(self.y - self.persistentTarget.y, 2));
if (searchDistance < 600) {
// Large search radius for persistent tracking
// Move directly toward the persistent target
var directAngle = Math.atan2(self.persistentTarget.y - self.y, self.persistentTarget.x - self.x);
// Check if dog is in pond and reduce speed
var dogPursuitSpeed = self.speed * 1.2;
for (var p = 0; p < ponds.length; p++) {
var pond = ponds[p];
var distanceToPond = Math.sqrt(Math.pow(self.x - pond.x, 2) + Math.pow(self.y - pond.y, 2));
if (distanceToPond < 100) {
// Dog is in or near pond
dogPursuitSpeed *= 0.4; // Reduce speed significantly in water
break;
}
}
self.x += Math.cos(directAngle) * dogPursuitSpeed;
self.y += Math.sin(directAngle) * dogPursuitSpeed;
self.graphics.rotation = directAngle;
self.wasChasing = true;
// Bark occasionally to signal pursuit
if (LK.ticks % 90 === 0) {
LK.getSound('bark').play();
}
// Flash to show active pursuit
if (LK.ticks % 60 === 0) {
LK.effects.flashObject(self, 0xFFAA00, 100); // Orange flash for long-distance pursuit
}
} else {
// Clear persistent target if too far away
self.persistentTarget = null;
}
} else if (self.isFollowing) {
// Follow footprints or hunter when not chasing
var distanceToHunter = Math.sqrt(Math.pow(self.x - hunter.x, 2) + Math.pow(self.y - hunter.y, 2));
// If dog was chasing but now stopped, smoothly return to player using tween
if (self.wasChasing && !closestAnimal) {
self.wasChasing = false;
// Use tween to smoothly move dog back to hunter
tween(self, {
x: hunter.x + self.followDistance * 0.6,
y: hunter.y + self.followDistance * 0.4
}, {
duration: 1500,
easing: tween.easeOut,
onFinish: function onFinish() {
// Dog has returned to player
LK.effects.flashObject(self, 0x00FF00, 300);
}
});
return; // Skip normal movement while tweening back
}
// Check for nearby footprints to follow and find hidden animals
var nearestFootprint = null;
var nearestFootprintDistance = Infinity;
var footprintTrail = []; // Store footprints for trail following
for (var f = 0; f < footprints.length; f++) {
var footprint = footprints[f];
var footprintDistance = Math.sqrt(Math.pow(self.x - footprint.x, 2) + Math.pow(self.y - footprint.y, 2));
if (footprintDistance < 150) {
footprintTrail.push({
footprint: footprint,
distance: footprintDistance
});
}
if (footprintDistance < nearestFootprintDistance) {
nearestFootprint = footprint;
nearestFootprintDistance = footprintDistance;
}
}
// Sort footprint trail by distance for better pathfinding
footprintTrail.sort(function (a, b) {
return a.distance - b.distance;
});
// Follow footprints if found, otherwise follow hunter
if (nearestFootprint && nearestFootprintDistance < 80) {
// Move towards footprint
var angle = Math.atan2(nearestFootprint.y - self.y, nearestFootprint.x - self.x);
// Check if dog is in pond and reduce speed
var dogSpeed = self.speed * 0.9;
for (var p = 0; p < ponds.length; p++) {
var pond = ponds[p];
var distanceToPond = Math.sqrt(Math.pow(self.x - pond.x, 2) + Math.pow(self.y - pond.y, 2));
if (distanceToPond < 100) {
// Dog is in or near pond
dogSpeed *= 0.4; // Reduce speed significantly in water
break;
}
}
self.x += Math.cos(angle) * dogSpeed;
self.y += Math.sin(angle) * dogSpeed;
// Flash footprint when following it
if (LK.ticks % 60 === 0) {
LK.effects.flashObject(nearestFootprint, 0xFFFF00, 200);
}
// When close to footprint, search for nearby hidden animals
if (nearestFootprintDistance < 30) {
for (var a = 0; a < animals.length; a++) {
var animal = animals[a];
if (!animal.isAlive || animal.isDiscoveredByDog) {
continue;
}
var animalToFootprintDistance = Math.sqrt(Math.pow(animal.x - nearestFootprint.x, 2) + Math.pow(animal.y - nearestFootprint.y, 2));
var animalToDogDistance = Math.sqrt(Math.pow(animal.x - self.x, 2) + Math.pow(animal.y - self.y, 2));
// If animal is near this footprint and dog is close, discover it
if (animalToFootprintDistance < 100 && animalToDogDistance < self.detectionRadius * 1.2) {
animal.isDiscoveredByDog = true;
animal.graphics.visible = true;
animal.visibilityTimer = 0;
animal.isFadingOut = false;
animal.graphics.alpha = 1.0;
// Special effect for footprint-based discovery
LK.effects.flashObject(animal, 0x00FFFF, 1000); // Cyan flash for trail discovery
LK.effects.flashObject(nearestFootprint, 0x00FFFF, 500);
// Play bark sound for discovery
if (LK.ticks % 30 === 0) {
LK.getSound('bark').play();
}
// Point dog towards discovered animal
var discoveryAngle = Math.atan2(animal.y - self.y, animal.x - self.x);
self.graphics.rotation = discoveryAngle;
break;
}
}
}
// Consume energy while tracking
if (LK.ticks % 25 === 0) {
self.energy = Math.max(0, self.energy - 0.2);
}
} else if (distanceToHunter > self.followDistance) {
// Move towards hunter
var angle = Math.atan2(hunter.y - self.y, hunter.x - self.x);
// Check if dog is in pond and reduce speed
var dogFollowSpeed = self.speed;
for (var p = 0; p < ponds.length; p++) {
var pond = ponds[p];
var distanceToPond = Math.sqrt(Math.pow(self.x - pond.x, 2) + Math.pow(self.y - pond.y, 2));
if (distanceToPond < 100) {
// Dog is in or near pond
dogFollowSpeed *= 0.4; // Reduce speed significantly in water
break;
}
}
self.x += Math.cos(angle) * dogFollowSpeed;
self.y += Math.sin(angle) * dogFollowSpeed;
// Consume energy while moving
if (LK.ticks % 20 === 0) {
self.energy = Math.max(0, self.energy - 0.3);
}
}
}
// Apply breed-specific abilities
var detectionBonus = 1.0;
var speedBonus = 1.0;
var energyDrain = 1.0;
if (self.specialAbility === 'sight') {
// Pointer - better long distance detection
detectionBonus = 1.3;
} else if (self.specialAbility === 'stealth') {
// Setter - animals don't flee as quickly
speedBonus = 1.1;
} else if (self.specialAbility === 'endurance') {
// Husky - less energy drain
energyDrain = 0.7;
} else if (self.specialAbility === 'tracking') {
// Retriever - better footprint following
detectionBonus = 1.1;
}
// Scan for animals and ducks and make them visible when detected
self.alertCooldown = Math.max(0, self.alertCooldown - 1);
var allAnimals = animals.concat(ducks);
for (var i = 0; i < allAnimals.length; i++) {
var animal = allAnimals[i];
if (!animal.isAlive) {
continue;
}
var distance = Math.sqrt(Math.pow(self.x - animal.x, 2) + Math.pow(self.y - animal.y, 2));
// Make animal visible if dog detects it (with breed bonus)
var effectiveDetectionRadius = self.detectionRadius * detectionBonus;
if (distance < effectiveDetectionRadius && !animal.isDiscoveredByDog) {
animal.isDiscoveredByDog = true;
animal.graphics.visible = true;
animal.visibilityTimer = 0; // Reset visibility timer
animal.isFadingOut = false; // Reset fading flag
animal.graphics.alpha = 1.0; // Ensure full visibility
// Set as persistent target only if dog doesn't have one, prioritizing closer animals
if (!self.persistentTarget || !self.persistentTarget.isAlive) {
self.persistentTarget = animal;
// Flash the animal when first discovered and set as target
LK.effects.flashObject(animal, 0x00FF00, 800);
} else {
// Check if this animal is closer than current persistent target
var currentTargetDistance = Math.sqrt(Math.pow(self.x - self.persistentTarget.x, 2) + Math.pow(self.y - self.persistentTarget.y, 2));
if (distance < currentTargetDistance) {
// Switch to closer target
self.persistentTarget = animal;
// Flash the new priority target
LK.effects.flashObject(animal, 0x00FF00, 800);
} else {
// Flash discovered but not prioritized animal differently
LK.effects.flashObject(animal, 0xFFFF00, 400);
}
}
}
// Update last known position of persistent target
if (self.persistentTarget && self.persistentTarget.isAlive) {
self.lastKnownTargetX = self.persistentTarget.x;
self.lastKnownTargetY = self.persistentTarget.y;
}
}
// Alert to closest animal
if (self.alertCooldown === 0) {
var closestAnimal = null;
var closestDistance = Infinity;
var allAnimals = animals.concat(ducks);
for (var i = 0; i < allAnimals.length; i++) {
var animal = allAnimals[i];
if (!animal.isAlive || !animal.isDiscoveredByDog) {
continue;
}
var distance = Math.sqrt(Math.pow(self.x - animal.x, 2) + Math.pow(self.y - animal.y, 2));
if (distance < self.detectionRadius && distance < closestDistance) {
closestAnimal = animal;
closestDistance = distance;
}
}
if (closestAnimal && closestAnimal !== self.targetAnimal) {
self.targetAnimal = closestAnimal;
self.alertToAnimal();
self.alertCooldown = 180; // 3 seconds cooldown
}
}
// Regenerate energy when not moving much
var movement = Math.sqrt(Math.pow(self.x - self.lastX, 2) + Math.pow(self.y - self.lastY, 2));
if (movement < 1 && self.energy < 100) {
self.energy += 0.15;
}
// Dog whines when energy is very low
if (self.energy < 20 && LK.ticks % 180 === 0) {
LK.getSound('dog_whine').play();
}
// Walking animation logic
var currentlyWalking = movement > 0.5; // Dog is walking if movement is significant
// Start walking animation when movement begins
if (!self.isWalking && currentlyWalking) {
self.isWalking = true;
self.startWalkAnimation();
}
// Stop walking animation when movement stops
else if (self.isWalking && !currentlyWalking) {
self.isWalking = false;
self.stopWalkAnimation();
}
self.lastX = self.x;
self.lastY = self.y;
};
self.alertToAnimal = function () {
// Visual alert - flash the dog
LK.effects.flashObject(self, 0xFFFF00, 500);
// Point towards the animal
if (self.targetAnimal) {
var angle = Math.atan2(self.targetAnimal.y - self.y, self.targetAnimal.x - self.x);
self.graphics.rotation = angle;
// Play bark sound
LK.getSound('bark').play();
// Create tracking bonus for player - larger bonus if found via footprints
var trackingBonus = 5;
// Check if there are recent footprints nearby (indicating trail-based discovery)
for (var f = 0; f < footprints.length; f++) {
var footprint = footprints[f];
var footprintToAnimalDistance = Math.sqrt(Math.pow(self.targetAnimal.x - footprint.x, 2) + Math.pow(self.targetAnimal.y - footprint.y, 2));
if (footprintToAnimalDistance < 80) {
trackingBonus = 10; // Double bonus for footprint-based discovery
break;
}
}
hunter.tracking = Math.min(100, hunter.tracking + trackingBonus);
}
};
self.rest = function () {
// Dog rests and recovers energy faster
self.energy = Math.min(100, self.energy + 2);
};
self.startWalkAnimation = function () {
// Create a continuous bobbing animation while walking
self.walkAnimationLoop();
};
self.stopWalkAnimation = function () {
// Stop the walking animation and return to normal position
tween.stop(self.graphics, {
y: true,
scaleY: true
});
tween(self.graphics, {
y: 0,
scaleY: 1.0
}, {
duration: 200,
easing: tween.easeOut
});
};
self.walkAnimationLoop = function () {
if (!self.isWalking) {
return;
} // Stop if no longer walking
// Create bobbing motion with slight scale changes to simulate walking
tween(self.graphics, {
y: -3,
scaleY: 0.95
}, {
duration: 300,
easing: tween.easeInOut,
onFinish: function onFinish() {
if (self.isWalking) {
// Bounce back down
tween(self.graphics, {
y: 0,
scaleY: 1.05
}, {
duration: 300,
easing: tween.easeInOut,
onFinish: function onFinish() {
// Continue the loop if still walking
if (self.isWalking) {
self.walkAnimationLoop();
}
}
});
}
}
});
};
self.switchBreed = function (breedData) {
self.breed = breedData.name;
self.speed = breedData.speed;
self.detectionRadius = breedData.detection;
self.maxEnergy = breedData.energy;
self.energy = Math.min(self.energy, self.maxEnergy);
self.specialAbility = breedData.specialAbility;
// Apply breed color tint
self.graphics.tint = breedData.color;
// Flash effect for breed change
LK.effects.flashObject(self, 0x00FF00, 1000);
};
return self;
});
var Shop = Container.expand(function () {
var self = Container.call(this);
self.isOpen = false;
// Shop background panel
self.background = self.addChild(LK.getAsset('shop_panel', {
anchorX: 0.5,
anchorY: 0.5
}));
self.background.scaleX = 1.2;
self.background.scaleY = 1.5;
// Shop title
self.title = self.addChild(new Text2('HUNTING SHOP', {
size: 32,
fill: 0xFFFFFF
}));
self.title.anchor.set(0.5, 0);
self.title.y = -450;
// Close button
self.closeButton = self.addChild(LK.getAsset('buy_button', {
anchorX: 0.5,
anchorY: 0.5
}));
self.closeButton.y = 420;
self.closeButton.tint = 0xFF4444;
self.closeText = self.addChild(new Text2('CLOSE', {
size: 18,
fill: 0xFFFFFF
}));
self.closeText.anchor.set(0.5, 0.5);
self.closeText.y = 420;
// Shop items
self.items = [];
self.itemsContainer = self.addChild(new Container());
self.itemsContainer.y = -350;
self.open = function () {
self.isOpen = true;
self.visible = true;
self.setupItems();
// Bring shop to front
if (self.parent) {
self.parent.removeChild(self);
self.parent.addChild(self);
}
};
self.close = function () {
self.isOpen = false;
self.visible = false;
};
self.setupItems = function () {
// Clear existing items
for (var i = 0; i < self.items.length; i++) {
if (self.items[i].parent) {
self.items[i].destroy();
}
}
self.items = [];
// Add shop items
var yOffset = 0;
for (var i = 0; i < shopItemsData.length; i++) {
var item = self.itemsContainer.addChild(new ShopItem(shopItemsData[i]));
item.y = yOffset;
item.x = -200; // Center the items
self.items.push(item);
yOffset += 100;
}
};
self.down = function (x, y, obj) {
if (!self.isOpen) {
return;
}
// Check close button
if (x >= -60 && x <= 60 && y >= 400 && y <= 440) {
LK.getSound('button_click').play();
self.close();
}
};
// Initially hidden
self.visible = false;
return self;
});
var ShopItem = Container.expand(function (itemData) {
var self = Container.call(this);
self.itemData = itemData;
self.name = itemData.name;
self.price = itemData.price;
self.description = itemData.description;
self.type = itemData.type;
self.effect = itemData.effect;
// Create item display
self.panel = self.addChild(LK.getAsset('shop_panel', {
anchorX: 0,
anchorY: 0
}));
self.panel.scaleX = 0.8;
self.panel.scaleY = 0.3;
// Item name
self.nameText = self.addChild(new Text2(self.name, {
size: 24,
fill: 0xFFFFFF
}));
self.nameText.anchor.set(0, 0);
self.nameText.x = 10;
self.nameText.y = 10;
// Item price
self.priceText = self.addChild(new Text2('$' + self.price, {
size: 20,
fill: 0xFFD700
}));
self.priceText.anchor.set(0, 0);
self.priceText.x = 10;
self.priceText.y = 35;
// Description
self.descText = self.addChild(new Text2(self.description, {
size: 16,
fill: 0xCCCCCC
}));
self.descText.anchor.set(0, 0);
self.descText.x = 10;
self.descText.y = 60;
// Buy button
self.buyButton = self.addChild(LK.getAsset('buy_button', {
anchorX: 0,
anchorY: 0
}));
self.buyButton.x = 250;
self.buyButton.y = 30;
self.buyText = self.addChild(new Text2('BUY', {
size: 18,
fill: 0xFFFFFF
}));
self.buyText.anchor.set(0.5, 0.5);
self.buyText.x = 310;
self.buyText.y = 50;
self.down = function (x, y, obj) {
// Check if click is on buy button
if (x >= 250 && x <= 370 && y >= 30 && y <= 70) {
LK.getSound('button_click').play();
if (playerCoins >= self.price) {
playerCoins -= self.price;
applyItemEffect(self);
coinText.setText('Coins: ' + playerCoins);
// Visual feedback
LK.effects.flashObject(self.buyButton, 0x00FF00, 300);
LK.getSound('coin_pickup').play();
} else {
// Not enough coins
LK.effects.flashObject(self.buyButton, 0xFF0000, 300);
}
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x2F4F2F // Dark forest green
});
/****
* Game Code
****/
// Game variables
// Hunter and equipment
// Animals
// Environment
// UI elements
// Sounds
var hunter;
var huntingDog;
var animals = [];
var trees = [];
var footprints = [];
var ponds = [];
var ducks = [];
var gameScore = 0;
var dayTime = 0; // 0-1440 (24 hours in minutes)
var isAiming = false;
// Shop system variables
var playerCoins = 100; // Starting coins
var shop;
var shopButton;
var coinText;
// Map system variables
var currentMap = 0; // 0=Forest, 1=Desert, 2=Arctic
var mapButton;
var mapText;
// Season system variables
var currentSeason = storage.currentSeason || 0; // 0=Spring, 1=Summer, 2=Autumn, 3=Winter
var seasonDuration = 3600; // 60 seconds per season at 60fps
var seasonTimer = storage.seasonTimer || 0;
var seasonButton;
var seasonText;
// Dog breed data for different seasons
var dogBreeds = [{
name: 'Retriever',
season: 'Spring',
speed: 2.5,
detection: 200,
energy: 100,
specialAbility: 'tracking',
color: 0xD2691E
}, {
name: 'Pointer',
season: 'Summer',
speed: 3.0,
detection: 250,
energy: 120,
specialAbility: 'sight',
color: 0xFFD700
}, {
name: 'Setter',
season: 'Autumn',
speed: 2.8,
detection: 180,
energy: 110,
specialAbility: 'stealth',
color: 0x8B4513
}, {
name: 'Husky',
season: 'Winter',
speed: 2.2,
detection: 220,
energy: 140,
specialAbility: 'endurance',
color: 0x708090
}];
var seasons = [{
name: 'Spring',
backgroundColor: 0x228B22,
visibility: 1.0,
animalSpawnRate: 1.2,
weatherEffect: 'rain'
}, {
name: 'Summer',
backgroundColor: 0x32CD32,
visibility: 0.8,
animalSpawnRate: 0.8,
weatherEffect: 'heat'
}, {
name: 'Autumn',
backgroundColor: 0xD2691E,
visibility: 0.9,
animalSpawnRate: 1.1,
weatherEffect: 'wind'
}, {
name: 'Winter',
backgroundColor: 0xF0F8FF,
visibility: 0.6,
animalSpawnRate: 0.7,
weatherEffect: 'snow'
}];
var maps = [{
name: 'Forest',
backgroundColor: 0x2F4F2F,
animals: ['deer', 'rabbit', 'boar', 'wolf'],
treeCount: 30,
bushCount: 50,
animalCount: 15,
pondCount: 3,
duckCount: 6,
visibility: 1.0
}, {
name: 'Desert',
backgroundColor: 0x8B7355,
animals: ['rabbit', 'boar'],
treeCount: 5,
bushCount: 20,
animalCount: 8,
pondCount: 1,
duckCount: 2,
visibility: 0.7
}, {
name: 'Arctic',
backgroundColor: 0xE6F3FF,
animals: ['deer', 'wolf'],
treeCount: 15,
bushCount: 10,
animalCount: 6,
pondCount: 2,
duckCount: 4,
visibility: 0.5
}];
// Shop items data
var shopItemsData = [{
name: 'Better Rifle',
price: 150,
description: 'Increases accuracy by 20%',
type: 'weapon',
effect: 'accuracy'
}, {
name: 'Camouflage Gear',
price: 100,
description: 'Improves stealth by 25%',
type: 'equipment',
effect: 'stealth'
}, {
name: 'Tracking Boots',
price: 80,
description: 'Better tracking skills +15%',
type: 'equipment',
effect: 'tracking'
}, {
name: 'Energy Drink',
price: 50,
description: 'Restores stamina to full',
type: 'consumable',
effect: 'stamina'
}, {
name: 'Ammo Pack',
price: 40,
description: 'Restores ammo to full',
type: 'consumable',
effect: 'ammo'
}, {
name: 'Food Rations',
price: 30,
description: 'Restores hunger to full',
type: 'consumable',
effect: 'hunger'
}];
// UI elements
var ammoBar, hungerBar, scoreText, timeText;
// Initialize hunter
hunter = game.addChild(new Hunter());
hunter.x = 1024;
hunter.y = 1366;
// Initialize hunting dog
huntingDog = game.addChild(new HuntingDog());
huntingDog.x = hunter.x + 50;
huntingDog.y = hunter.y + 30;
// Set initial dog breed based on current season
var initialBreed = dogBreeds[currentSeason];
huntingDog.switchBreed(initialBreed);
// Initialize first map
createMapEnvironment(maps[currentMap]);
// Create UI
var ammoBar = LK.getAsset('ammo_bar', {
anchorX: 0,
anchorY: 0
});
LK.gui.topLeft.addChild(ammoBar);
ammoBar.x = 120;
ammoBar.y = 20;
// Ammo label
var ammoText = new Text2('Ammo', {
size: 16,
fill: 0xFFFFFF
});
ammoText.anchor.set(0, 0);
LK.gui.topLeft.addChild(ammoText);
ammoText.x = 120;
ammoText.y = 5;
hungerBar = LK.getAsset('hunger_bar', {
anchorX: 0,
anchorY: 0
});
LK.gui.topLeft.addChild(hungerBar);
hungerBar.x = 120;
hungerBar.y = 50;
// Dog energy bar
var dogEnergyBar = LK.getAsset('stamina_bar', {
anchorX: 0,
anchorY: 0
});
LK.gui.topLeft.addChild(dogEnergyBar);
dogEnergyBar.x = 120;
dogEnergyBar.y = 80;
dogEnergyBar.tint = 0xFFD700; // Golden color for dog
// Dog energy label
var dogEnergyText = new Text2('Dog Energy', {
size: 16,
fill: 0xFFFFFF
});
dogEnergyText.anchor.set(0, 0);
LK.gui.topLeft.addChild(dogEnergyText);
dogEnergyText.x = 120;
dogEnergyText.y = 105;
scoreText = new Text2('Score: 0', {
size: 40,
fill: 0xFFFFFF
});
scoreText.anchor.set(0, 0);
LK.gui.topRight.addChild(scoreText);
scoreText.x = -200;
scoreText.y = 20;
timeText = new Text2('Dawn', {
size: 35,
fill: 0xFFD700
});
timeText.anchor.set(0.5, 0);
LK.gui.top.addChild(timeText);
timeText.y = 20;
// Coin display
coinText = new Text2('Coins: ' + playerCoins, {
size: 40,
fill: 0xFFD700
});
coinText.anchor.set(1, 0);
LK.gui.topRight.addChild(coinText);
coinText.x = -10;
coinText.y = 60;
// Shop button
shopButton = LK.getAsset('shop_button', {
anchorX: 0.5,
anchorY: 0.5
});
LK.gui.bottomRight.addChild(shopButton);
shopButton.x = -100;
shopButton.y = -50;
var shopButtonText = new Text2('SHOP', {
size: 24,
fill: 0xFFFFFF
});
shopButtonText.anchor.set(0.5, 0.5);
LK.gui.bottomRight.addChild(shopButtonText);
shopButtonText.x = -100;
shopButtonText.y = -50;
// Initialize shop
shop = game.addChild(new Shop());
shop.x = 1024;
shop.y = 1366;
shop.visible = false;
// Map button
mapButton = LK.getAsset('shop_button', {
anchorX: 0.5,
anchorY: 0.5
});
LK.gui.bottomLeft.addChild(mapButton);
mapButton.x = 100;
mapButton.y = -50;
mapText = new Text2('MAP: ' + maps[currentMap].name, {
size: 20,
fill: 0xFFFFFF
});
mapText.anchor.set(0.5, 0.5);
LK.gui.bottomLeft.addChild(mapText);
mapText.x = 100;
mapText.y = -50;
// Season button
seasonButton = LK.getAsset('shop_button', {
anchorX: 0.5,
anchorY: 0.5
});
LK.gui.bottomLeft.addChild(seasonButton);
seasonButton.x = 100;
seasonButton.y = -120;
seasonText = new Text2('SEASON: ' + seasons[currentSeason].name, {
size: 18,
fill: 0xFFFFFF
});
seasonText.anchor.set(0.5, 0.5);
LK.gui.bottomLeft.addChild(seasonText);
seasonText.x = 100;
seasonText.y = -120;
// Create footprint function
function createFootprint(x, y) {
var footprint = game.addChild(new Footprint());
footprint.x = x;
footprint.y = y;
footprints.push(footprint);
}
// Function to switch seasons
function switchToSeason(seasonIndex) {
currentSeason = seasonIndex;
var seasonData = seasons[currentSeason];
var breedData = dogBreeds[currentSeason];
// Update background color to season theme
var mapData = maps[currentMap];
var seasonColor = seasonData.backgroundColor;
game.setBackgroundColor(seasonColor);
// Switch dog breed
if (huntingDog) {
huntingDog.switchBreed(breedData);
}
// Update UI
seasonText.setText('SEASON: ' + seasonData.name);
// Save to storage
storage.currentSeason = currentSeason;
storage.seasonTimer = 0;
seasonTimer = 0;
// Visual effects for season change
LK.effects.flashScreen(seasonColor, 1500);
}
// Function to switch maps
function switchToMap(mapIndex) {
currentMap = mapIndex;
var mapData = maps[currentMap];
// Apply current season's background color
var seasonData = seasons[currentSeason];
game.setBackgroundColor(seasonData.backgroundColor);
// Clear existing environment
for (var i = trees.length - 1; i >= 0; i--) {
if (trees[i].parent) {
trees[i].destroy();
}
}
trees = [];
// Clear existing animals
for (var i = animals.length - 1; i >= 0; i--) {
if (animals[i].parent) {
animals[i].destroy();
}
}
animals = [];
// Clear footprints
for (var i = footprints.length - 1; i >= 0; i--) {
if (footprints[i].parent) {
footprints[i].destroy();
}
}
footprints = [];
// Clear existing ponds
for (var i = ponds.length - 1; i >= 0; i--) {
if (ponds[i].parent) {
ponds[i].destroy();
}
}
ponds = [];
// Clear existing ducks
for (var i = ducks.length - 1; i >= 0; i--) {
if (ducks[i].parent) {
ducks[i].destroy();
}
}
ducks = [];
// Create new environment
createMapEnvironment(mapData);
// Update UI
mapText.setText('MAP: ' + mapData.name);
// Reset hunter position
hunter.x = 1024;
hunter.y = 1366;
// Reset dog position near hunter
if (huntingDog) {
huntingDog.x = hunter.x + 50;
huntingDog.y = hunter.y + 30;
huntingDog.energy = 100;
huntingDog.targetAnimal = null;
}
}
// Function to create environment for current map
function createMapEnvironment(mapData) {
// Create trees
for (var i = 0; i < mapData.treeCount; i++) {
var tree = game.addChild(LK.getAsset('tree', {
anchorX: 0.5,
anchorY: 1.0
}));
tree.x = Math.random() * 1948 + 50;
tree.y = Math.random() * 2582 + 100;
// Apply map-specific tinting
if (currentMap === 1) {
// Desert
tree.tint = 0xD2B48C; // Sandy brown
} else if (currentMap === 2) {
// Arctic
tree.tint = 0xC0C0C0; // Silver/white
}
trees.push(tree);
}
// Add bushes
for (var i = 0; i < mapData.bushCount; i++) {
var bush = game.addChild(LK.getAsset('bush', {
anchorX: 0.5,
anchorY: 0.5
}));
bush.x = Math.random() * 1948 + 50;
bush.y = Math.random() * 2582 + 100;
// Apply map-specific tinting
if (currentMap === 1) {
// Desert
bush.tint = 0xDAA520; // Golden rod
} else if (currentMap === 2) {
// Arctic
bush.tint = 0xF0F8FF; // Alice blue
}
}
// Spawn animals for this map
for (var i = 0; i < mapData.animalCount; i++) {
var animalType = mapData.animals[Math.floor(Math.random() * mapData.animals.length)];
var animal = game.addChild(new Animal(animalType));
animal.x = Math.random() * 1848 + 100;
animal.y = Math.random() * 2482 + 100;
// Reset visibility for new map
animal.isDiscoveredByDog = false;
animal.graphics.visible = false;
animal.graphics.alpha = 1.0; // Reset transparency
animal.visibilityTimer = 0; // Reset visibility timer
animal.isFadingOut = false; // Reset fading flag
animals.push(animal);
}
// Create ponds
for (var i = 0; i < mapData.pondCount; i++) {
var pond = game.addChild(LK.getAsset('pond', {
anchorX: 0.5,
anchorY: 0.5
}));
pond.x = Math.random() * 1648 + 200; // Keep ponds away from edges
pond.y = Math.random() * 2382 + 200;
// Apply map-specific tinting
if (currentMap === 1) {
// Desert - more muddy water
pond.tint = 0x8FBC8F;
} else if (currentMap === 2) {
// Arctic - icy water
pond.tint = 0xAFEEEE;
}
ponds.push(pond);
}
// Spawn ducks in ponds
var ducksPerPond = Math.floor(mapData.duckCount / mapData.pondCount);
var extraDucks = mapData.duckCount % mapData.pondCount;
for (var i = 0; i < mapData.pondCount; i++) {
var pond = ponds[i];
var ducksForThisPond = ducksPerPond + (i < extraDucks ? 1 : 0);
for (var j = 0; j < ducksForThisPond; j++) {
var duck = game.addChild(new Duck());
// Position duck near pond center with some randomness
duck.x = pond.x + (Math.random() - 0.5) * 60;
duck.y = pond.y + (Math.random() - 0.5) * 60;
duck.pondX = pond.x;
duck.pondY = pond.y;
// Start ducks as invisible until dog finds them
duck.isDiscoveredByDog = false;
duck.graphics.visible = false;
duck.graphics.alpha = 1.0;
duck.visibilityTimer = 0;
duck.isFadingOut = false;
ducks.push(duck);
}
}
}
// Shop item effects function
function applyItemEffect(shopItem) {
var effect = shopItem.effect;
var type = shopItem.type;
if (type === 'consumable') {
if (effect === 'ammo') {
hunter.ammo = hunter.maxAmmo;
LK.getSound('reload').play();
} else if (effect === 'hunger') {
hunter.hunger = 100;
}
} else if (type === 'equipment' || type === 'weapon') {
if (effect === 'accuracy') {
hunter.accuracy = Math.min(100, hunter.accuracy + 20);
} else if (effect === 'stealth') {
hunter.stealth = Math.min(100, hunter.stealth + 25);
} else if (effect === 'tracking') {
hunter.tracking = Math.min(100, hunter.tracking + 15);
}
}
}
// Function to award coins for hunting
function awardCoins(animalType) {
var coinReward = 5;
if (animalType === 'deer') {
coinReward = 10;
} else if (animalType === 'boar') {
coinReward = 15;
} else if (animalType === 'wolf') {
coinReward = 25;
} else if (animalType === 'duck') {
coinReward = 12;
}
playerCoins += coinReward;
coinText.setText('Coins: ' + playerCoins);
// Create coin visual effect
var coinEffect = game.addChild(LK.getAsset('coin', {
anchorX: 0.5,
anchorY: 0.5
}));
coinEffect.x = hunter.x;
coinEffect.y = hunter.y - 50;
// Animate coin moving up and fading
tween(coinEffect, {
y: coinEffect.y - 100,
alpha: 0
}, {
duration: 1000,
onFinish: function onFinish() {
if (coinEffect.parent) {
coinEffect.destroy();
}
}
});
}
// Shooting function
function shootAtTarget(targetX, targetY) {
if (hunter.ammo <= 0) {
return;
}
// hunter.ammo -= 1; // Infinite ammo - commented out ammo consumption
LK.getSound('gunshot').play();
// Check if shot is blocked by trees
var shotBlocked = false;
for (var t = 0; t < trees.length; t++) {
var tree = trees[t];
// Calculate if the shot line intersects with the tree
var dx = targetX - hunter.x;
var dy = targetY - hunter.y;
var shotLength = Math.sqrt(dx * dx + dy * dy);
// Normalize the shot direction
var shotDirX = dx / shotLength;
var shotDirY = dy / shotLength;
// Check multiple points along the shot path
for (var step = 0; step < shotLength; step += 10) {
var checkX = hunter.x + shotDirX * step;
var checkY = hunter.y + shotDirY * step;
var treeDistance = Math.sqrt(Math.pow(checkX - tree.x, 2) + Math.pow(checkY - tree.y, 2));
if (treeDistance < 50) {
// Tree collision radius
shotBlocked = true;
break;
}
}
if (shotBlocked) {
break;
}
}
// If shot is blocked, don't check for animal hits
if (shotBlocked) {
return;
}
// Check if we hit any animals or ducks
var allTargets = animals.concat(ducks);
for (var i = allTargets.length - 1; i >= 0; i--) {
var animal = allTargets[i];
var distance = Math.sqrt(Math.pow(animal.x - targetX, 2) + Math.pow(animal.y - targetY, 2));
if (distance < 60) {
// Hit!
animal.isAlive = false;
animal.graphics.alpha = 0.5;
LK.getSound('animal_hurt').play();
// Clear dog's persistent target if this animal was being chased
if (huntingDog && huntingDog.persistentTarget === animal) {
huntingDog.persistentTarget = null;
huntingDog.wasChasing = false;
// Flash dog to show successful hunt completion
LK.effects.flashObject(huntingDog, 0x00FF00, 500);
}
// Award points based on animal type
var points = 10;
if (animal.animalType === 'deer') {
points = 20;
} else if (animal.animalType === 'boar') {
points = 30;
} else if (animal.animalType === 'wolf') {
points = 50;
} else {
points = 15;
} // Duck points
gameScore += points;
LK.setScore(gameScore);
scoreText.setText('Score: ' + gameScore);
// Award coins
if (animal.animalType) {
awardCoins(animal.animalType);
} else {
awardCoins('duck'); // For ducks
}
// Flash effect
LK.effects.flashObject(animal, 0xff0000, 500);
// Remove animal after delay
LK.setTimeout(function () {
if (animal.parent) {
animal.destroy();
// Remove from appropriate array
if (animal.animalType) {
for (var j = animals.length - 1; j >= 0; j--) {
if (animals[j] === animal) {
animals.splice(j, 1);
break;
}
}
} else {
for (var j = ducks.length - 1; j >= 0; j--) {
if (ducks[j] === animal) {
ducks.splice(j, 1);
break;
}
}
}
}
}, 1000);
break;
}
}
}
// Movement handling
var targetX = hunter.x;
var targetY = hunter.y;
var isMovingToTarget = false;
function handleMove(x, y, obj) {
targetX = x;
targetY = y;
isMovingToTarget = true;
}
game.move = handleMove;
game.down = function (x, y, obj) {
// Convert to GUI coordinates for shop button check - with null check
if (obj && obj.parent && obj.position) {
var guiPos = LK.gui.bottomRight.toLocal(obj.parent.toGlobal(obj.position));
// Check if shop button was clicked
if (guiPos.x >= -175 && guiPos.x <= -25 && guiPos.y >= -75 && guiPos.y <= -25) {
console.log("Shop button clicked!");
LK.getSound('button_click').play();
if (!shop.isOpen) {
shop.open();
console.log("Shop opened, visible:", shop.visible);
}
return;
}
// Check if map button was clicked
var mapGuiPos = LK.gui.bottomLeft.toLocal(obj.parent.toGlobal(obj.position));
if (mapGuiPos.x >= 25 && mapGuiPos.x <= 175 && mapGuiPos.y >= -75 && mapGuiPos.y <= -25) {
console.log("Map button clicked!");
LK.getSound('button_click').play();
currentMap = (currentMap + 1) % maps.length;
switchToMap(currentMap);
LK.effects.flashObject(mapButton, 0x00FF00, 300);
return;
}
// Check if season button was clicked
if (mapGuiPos.x >= 25 && mapGuiPos.x <= 175 && mapGuiPos.y >= -145 && mapGuiPos.y <= -95) {
console.log("Season button clicked!");
LK.getSound('button_click').play();
currentSeason = (currentSeason + 1) % seasons.length;
switchToSeason(currentSeason);
LK.effects.flashObject(seasonButton, 0x00FF00, 300);
return;
}
}
// Check if clicking on the dog to give it rest command
var dogDistance = Math.sqrt(Math.pow(huntingDog.x - x, 2) + Math.pow(huntingDog.y - y, 2));
if (dogDistance < 60) {
huntingDog.rest();
LK.effects.flashObject(huntingDog, 0x00FF00, 300);
return;
}
if (isAiming) {
shootAtTarget(x, y);
isAiming = false;
} else {
isAiming = true;
LK.setTimeout(function () {
isAiming = false;
}, 2000);
}
};
// Main game update loop
game.update = function () {
// Update season progression
seasonTimer++;
storage.seasonTimer = seasonTimer;
// Automatic season change every seasonDuration ticks
if (seasonTimer >= seasonDuration) {
var nextSeason = (currentSeason + 1) % seasons.length;
switchToSeason(nextSeason);
}
// Update day/night cycle
dayTime += 0.5; // 0.5 minutes per frame at 60fps = 48 minutes real time for full day
if (dayTime >= 1440) {
dayTime = 0;
}
// Update time display
var hour = Math.floor(dayTime / 60);
var timeOfDay = '';
if (hour >= 5 && hour < 12) {
timeOfDay = 'Morning';
} else if (hour >= 12 && hour < 18) {
timeOfDay = 'Afternoon';
} else if (hour >= 18 && hour < 21) {
timeOfDay = 'Evening';
} else {
timeOfDay = 'Night';
}
timeText.setText(timeOfDay + ' (' + hour + ':' + Math.floor(dayTime % 60).toString().padStart(2, '0') + ')');
// Move hunter towards target
if (isMovingToTarget) {
var dx = targetX - hunter.x;
var dy = targetY - hunter.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 5) {
var moveSpeed = 3;
var newX = hunter.x + dx / distance * moveSpeed;
var newY = hunter.y + dy / distance * moveSpeed;
// Check if new position would be inside any pond
var canMove = true;
for (var p = 0; p < ponds.length; p++) {
var pond = ponds[p];
var distanceToPond = Math.sqrt(Math.pow(newX - pond.x, 2) + Math.pow(newY - pond.y, 2));
if (distanceToPond < 100) {
// Pond collision radius
canMove = false;
break;
}
}
// Only move if not colliding with pond
if (canMove) {
hunter.x = newX;
hunter.y = newY;
// Consume hunger while moving
if (LK.ticks % 10 === 0) {
hunter.hunger = Math.max(0, hunter.hunger - 0.1);
}
// Play footstep sound occasionally
if (LK.ticks % 30 === 0) {
LK.getSound('footstep').play();
}
} else {
// Stop moving if hitting pond
isMovingToTarget = false;
}
} else {
isMovingToTarget = false;
}
}
// Update UI bars
ammoBar.scaleX = hunter.ammo / hunter.maxAmmo;
hungerBar.scaleX = hunter.hunger / 100;
dogEnergyBar.scaleX = huntingDog.energy / 100;
// Update hunting dog
if (huntingDog) {
huntingDog.update();
}
// Update animals
for (var i = 0; i < animals.length; i++) {
animals[i].update();
}
// Update ducks
for (var i = 0; i < ducks.length; i++) {
ducks[i].update();
}
// Update footprints
for (var i = 0; i < footprints.length; i++) {
footprints[i].update();
}
// Spawn new animals occasionally (affected by season)
var mapData = maps[currentMap];
var seasonData = seasons[currentSeason];
var adjustedAnimalCount = Math.floor(mapData.animalCount * seasonData.animalSpawnRate);
var spawnRate = Math.floor(600 / seasonData.animalSpawnRate);
if (animals.length < adjustedAnimalCount && LK.ticks % spawnRate === 0) {
var animalType = mapData.animals[Math.floor(Math.random() * mapData.animals.length)];
var animal = game.addChild(new Animal(animalType));
animal.x = Math.random() * 1848 + 100;
animal.y = Math.random() * 2482 + 100;
// Start new animals as invisible until dog finds them
animal.isDiscoveredByDog = false;
animal.graphics.visible = false;
animal.graphics.alpha = 1.0; // Reset transparency
animal.visibilityTimer = 0; // Reset visibility timer
animal.isFadingOut = false; // Reset fading flag
animals.push(animal);
}
// Spawn new ducks occasionally
if (ducks.length < mapData.duckCount && LK.ticks % 800 === 0) {
if (ponds.length > 0) {
var randomPond = ponds[Math.floor(Math.random() * ponds.length)];
var duck = game.addChild(new Duck());
duck.x = randomPond.x + (Math.random() - 0.5) * 60;
duck.y = randomPond.y + (Math.random() - 0.5) * 60;
duck.pondX = randomPond.x;
duck.pondY = randomPond.y;
// Start new ducks as invisible until dog finds them
duck.isDiscoveredByDog = false;
duck.graphics.visible = false;
duck.graphics.alpha = 1.0;
duck.visibilityTimer = 0;
duck.isFadingOut = false;
ducks.push(duck);
}
}
// Game over condition - if hunter runs out of hunger
if (hunter.hunger <= 0) {
LK.effects.flashScreen(0xff0000, 1000);
LK.showGameOver();
}
};
Wild Boar. In-Game asset. 2d. High contrast. No shadows
Bush. In-Game asset. 2d. High contrast. No shadows
Tree. In-Game asset. 2d. High contrast. No shadows
Deer. In-Game asset. 2d. High contrast. No shadows
Hunter. In-Game asset. 2d. High contrast. No shadows
Rifle. In-Game asset. 2d. High contrast. No shadows
Wolf. In-Game asset. 2d. High contrast. No shadows
Rabbit. High contrast. No shadows
Coin. In-Game asset. 2d. High contrast. No shadows
Animal Footprint. High contrast. No shadows
Shop Button. In-Game asset. 2d. High contrast. No shadows
Duck Flying. In-Game asset. 2d. No shadows