bug session

This commit is contained in:
vesta 2025-09-10 22:38:09 +02:00
parent 3bcf836c63
commit a82cabac15

View file

@ -1,5 +1,5 @@
document.addEventListener('DOMContentLoaded', () => {
const Biome = {
const BIOME_TYPE = {
WATER_DEEP: { sprite:null, design: {frames: 8,duration: 200, drawer: drawDeepWaterFrame}, acceptStructure:false, movements:['swim'], affinities:[('water',0.8),('dark',0.2)], name: 'Eau Profonde', winterColor: '#3D5A80', fallColor: '#3D5A80', summerColor: '#3D5A80', autumnColor: '#3D5A80',maxElevation:0,minElevation:0 },
WATER_SHALLOW: { sprite:null, design: {frames: 6,duration: 150, drawer: drawWaterFrame}, acceptStructure:false,movements:['swim','ride','navigate','fly'], affinities:[('water',0.8),('life',0.2)],name: 'Eau Peu Profonde', winterColor: '#97aabdff', fallColor: '#5A8AB8', summerColor: '#5A8AB8', autumnColor: '#5A8AB8',maxElevation:1,minElevation:1 },
BEACH: { sprite:null, design: {frames: 4,duration: 200, drawer: drawBeachFrame}, acceptStructure:true, movements:['walk','ride','fly'],affinities:[('sand',0.8),('water',0.2)],name: 'Sable', winterColor: '#E9D9A1', fallColor: '#E9D9A1', summerColor: '#E9D9A1', autumnColor: '#E9D9A1',maxElevation:2 ,minElevation:2 },
@ -103,9 +103,9 @@ document.addEventListener('DOMContentLoaded', () => {
};
const ANIMAL_TYPES = {
WOLF: { name: 'Loup', movement: 'walk', svgAsset: () => wolfPackSVG, hp: 20, strength: 4, xp: 15, loot: { 'Cuir': 1, 'Os': 1 }, biomes: [Biome.FOREST, Biome.MOUNTAIN, Biome.SNOWLAND], spawnChance: 0.01, size: { w: 40, h: 40 }, offset: { x: -20, y: -35 } },
BOAR: { name: 'Sanglier', movement: 'walk', svgAsset: () => boarSVG, hp: 25, strength: 5, xp: 20, loot: { 'Cuir': 2 }, biomes: [Biome.FOREST, Biome.GRASSLAND], spawnChance: 0.02, size: { w: 30, h: 30 }, offset: { x: -15, y: -28 } },
BIRD: { name: 'Aigle', movement: 'fly', svgAsset: () => birdSVG, hp: 10, strength: 1, xp: 5, loot: { 'Plume': 1 }, biomes: [Biome.FOREST, Biome.GRASSLAND, Biome.MOUNTAIN, Biome.BEACH], spawnChance: 0.03, size: { w: 15, h: 12 }, offset: { x: -22, y: -45 }, flightHeight: 40 },
WOLF: { name: 'Loup', movement: 'walk', svgAsset: () => wolfPackSVG, hp: 20, strength: 4, xp: 15, loot: { 'Cuir': 1, 'Os': 1 }, biomes: [BIOME_TYPE.FOREST, BIOME_TYPE.MOUNTAIN, BIOME_TYPE.SNOWLAND], spawnChance: 0.01, size: { w: 40, h: 40 }, offset: { x: -20, y: -35 } },
BOAR: { name: 'Sanglier', movement: 'walk', svgAsset: () => boarSVG, hp: 25, strength: 5, xp: 20, loot: { 'Cuir': 2 }, biomes: [BIOME_TYPE.FOREST, BIOME_TYPE.GRASSLAND], spawnChance: 0.02, size: { w: 30, h: 30 }, offset: { x: -15, y: -28 } },
BIRD: { name: 'Aigle', movement: 'fly', svgAsset: () => birdSVG, hp: 10, strength: 1, xp: 5, loot: { 'Plume': 1 }, biomes: [BIOME_TYPE.FOREST, BIOME_TYPE.GRASSLAND, BIOME_TYPE.MOUNTAIN, BIOME_TYPE.BEACH], spawnChance: 0.03, size: { w: 15, h: 12 }, offset: { x: -22, y: -45 }, flightHeight: 40 },
};
// --- Global Asset Variables ---
@ -322,49 +322,35 @@ document.addEventListener('DOMContentLoaded', () => {
ctx.restore();
}
}
class Tile {
constructor(x, y) {
this.position = new Position(x, y);
this.biome = null;
this.structure = null;
this.animal = null;
this.visibility = 0; // 0: Unseen, 1: Seen, 2: Visible
this.setBiome();
this.setElevation();
this.setStructure();
this.setEntity();
class Biome {
constructor() {
this.type=null;
}
setBiome() {
setType(position){
let scale = 0.05;
let eRaw = (simplex.noise2D(this.position.x * scale, this.position.y * scale) + 1) / 2;
let tRaw = (simplex.noise2D(this.position.x * scale * 0.8, this.position.y * scale * 0.8) + 1) / 2;
let mRaw = (simplex.noise2D(this.position.x * scale * 1.5, this.position.y * scale * 1.5) + 1) / 2;
if (eRaw < 0.25) this.biome = Biome.WATER_DEEP;
else if (eRaw < 0.3) this.biome = Biome.WATER_SHALLOW;
else if (eRaw < 0.32) this.biome = Biome.SWAMP;
else if (eRaw < 0.35) this.biome = Biome.BEACH;
else if (eRaw > 0.85) this.biome = Biome.SNOWMOUNTAIN;
else if (eRaw > 0.75) this.biome = Biome.MOUNTAIN;
let eRaw = (simplex.noise2D(position.x * scale, position.y * scale) + 1) / 2;
let tRaw = (simplex.noise2D(position.x * scale * 0.8, position.y * scale * 0.8) + 1) / 2;
let mRaw = (simplex.noise2D(position.x * scale * 1.5, position.y * scale * 1.5) + 1) / 2;
if (eRaw < 0.25) this.type = BIOME_TYPE.WATER_DEEP;
else if (eRaw < 0.3) this.type = BIOME_TYPE.WATER_SHALLOW;
else if (eRaw < 0.32) this.type = BIOME_TYPE.SWAMP;
else if (eRaw < 0.35) this.type = BIOME_TYPE.BEACH;
else if (eRaw > 0.85) this.type = BIOME_TYPE.SNOWMOUNTAIN;
else if (eRaw > 0.75) this.type = BIOME_TYPE.MOUNTAIN;
else {
if (tRaw < 0.3) this.biome = Biome.DESERT;
else if (tRaw > 0.6) this.biome = (mRaw > 0.7) ? Biome.ENCHANTED_FOREST : Biome.FOREST;
else this.biome = Biome.GRASSLAND;
if (tRaw < 0.3) this.type = BIOME_TYPE.DESERT;
else if (tRaw > 0.6) this.type = (mRaw > 0.7) ? BIOME_TYPE.ENCHANTED_FOREST : BIOME_TYPE.FOREST;
else this.type = BIOME_TYPE.GRASSLAND;
}
}
setDesign(ctx){
if (!this.biome || this.visibility === 0) return;
const screenPos = this.position.cartToIso();
const elevationHeight = this.position.h * ELEVATION_STEP;
};
setDesign(ctx, position){
const screenPos = position.cartToIso();
const elevationHeight = position.h * ELEVATION_STEP;
ctx.save();
ctx.translate(screenPos.x, screenPos.y - elevationHeight);
const baseColor = this.biome.summerColor;
const baseColor = this.type.summerColor;
const shadowColor = shadeColor(baseColor, -30);
// Draw tile faces
@ -390,20 +376,53 @@ document.addEventListener('DOMContentLoaded', () => {
ctx.closePath();
ctx.fill();
}
if (this.type === BIOME_TYPE.FOREST && forestSVG) {
ctx.drawImage(forestSVG, -25, -25, 50, 50);
} else if (this.biome === BIOME_TYPE.ENCHANTED_FOREST && enchantedForestSVG) {
ctx.drawImage(enchantedForestSVG, -25, -25, 50, 50);
} else if (this.biome === BIOME_TYPE.SWAMP && swampSVG) {
ctx.drawImage(swampSVG, -25, -5, 50, 30);
}
};
}
class Tile {
constructor(x, y) {
this.position = new Position(x, y);
this.biome = null;
this.structure = null;
this.animal = null;
this.npc=[];
this.visibility = 0; // 0: Unseen, 1: Seen, 2: Visible
this.setBiome();
this.setElevation();
this.setStructure();
this.setEntity();
}
setBiome() {
this.biome=new Biome();
this.biome.setType(this.position);
}
setDesign(ctx){
if (!this.biome || this.visibility === 0) return;
this.biome.setDesign(ctx,this.position);
// Draw features if fully visible
if(this.visibility === 2) {
if (this.biome === Biome.FOREST && forestSVG) {
ctx.drawImage(forestSVG, -25, -25, 50, 50);
} else if (this.biome === Biome.ENCHANTED_FOREST && enchantedForestSVG) {
ctx.drawImage(enchantedForestSVG, -25, -25, 50, 50);
} else if (this.biome === Biome.SWAMP && swampSVG) {
ctx.drawImage(swampSVG, -25, -5, 50, 30);
}
if (this.structure ) {
this.structure.setDesign(ctx);
}
if (this.animal ) {
this.animal.setDesign(ctx);
}
if (this.npc ) {
this.npc.setDesign(ctx);
}
}
// Draw fog overlay if seen but not currently visible
@ -448,7 +467,7 @@ document.addEventListener('DOMContentLoaded', () => {
const animalType = ANIMAL_TYPES[key];
if (animalType.biomes.includes(this.biome) && Math.random() < animalType.spawnChance) {
if( this.biome.movements.includes(animalType.movement)) {
this.animal.push(new Animal(animalType, this.position));
this.animal = new Animal(animalType, this.position);
}
}
}
@ -591,7 +610,7 @@ document.addEventListener('DOMContentLoaded', () => {
const newY = this.settlement.position.y + y;
if (newX >= 0 && newX < gameMap.size && newY >= 0 && newY < gameMap.size) {
const targetTile = gameMap.tiles[newY][newX];
if(targetTile.biome.movements.includes('walk') && !targetTile.structure) {
if(targetTile.biome.type.movements.includes('walk') && !targetTile.structure) {
possibleMoves.push(targetTile);
}
}
@ -625,7 +644,7 @@ document.addEventListener('DOMContentLoaded', () => {
this.creature=creature;
this.equipments=equipments;
}
setDesign(ctx) {
const position = this.creature.position
if(!position) return;
@ -648,22 +667,323 @@ document.addEventListener('DOMContentLoaded', () => {
const newY = this.creature.position.y + dy;
if (newX >= 0 && newX < gameMap.size && newY >= 0 && newY < gameMap.size) {
const targetTile = gameMap.tiles[newY][newX].position;
if (targetTile.biome && targetTile.biome.movements.includes('walk')) {
if (targetTile.biome && targetTile.biome.type.movements.includes('walk')) {
this.creature.position = targetTile;
}
}
}
}
class Combat {
constructor(player, opponent) {
this.player=player;
this.opponent=opponent;
this.state='initiating'
this.startCombat();
}
startCombat() {
this.state= 'started';
document.getElementById('combat-screen').classList.remove('hidden');
document.getElementById('combat-screen').classList.add('flex');
updateCombatUI();
document.getElementById('combat-log').textContent = `Le combat commence`;
document.getElementById('attack-btn').addEventListener('click', handleAttack);
}
handleAttack(camera) {
const playerDamage = Math.max(1, player.attributes.strength + Math.floor(Math.random() * 4) - this.opponent.attributes.defense);
this.opponent.currentHp = Math.max(0, this.opponent.currentHp - playerDamage);
document.getElementById('combat-log').textContent = `Vous infligez ${playerDamage} dégâts.`;
floatingTexts.push({ content: `-${playerDamage}`, x: camera.x + canvas.width * 0.7, y: camera.y + canvas.height * 0.4, endTime: performance.now() + 1000, duration: 1000, color: '#ff4444' });
updateCombatUI();
if (this.opponent.currentHp <= 0) {
endCombat(true);
return;
}
setTimeout(() => {
const opponentDamage = Math.max(1, this.opponent.attributes.strength + Math.floor(Math.random() * 4) - 2);
this.player.creature.hp = Math.max(0, this.player.creature.hp - opponentDamage);
document.getElementById('combat-log').textContent += `\nLe ${this.opponent.name} riposte et vous inflige ${opponentDamage} dégâts.`;
floatingTexts.push({ content: `-${opponentDamage}`, x: camera.x + canvas.width * 0.3, y: camera.y + canvas.height * 0.4, endTime: performance.now() + 1000, duration: 1000, color: '#ff4444' });
updateCombatUI();
updateAllInfoPanels();
if ( this.player.creature.hp <= 0) {
endCombat(false);
}
}, 500);
}
endCombat(isVictory) {
const combatLog = document.getElementById('combat-log');
if (isVictory) {
combatLog.textContent = `Vous avez vaincu le ${currentOpponent.name} !`;
addXp(currentOpponent.xp);
Object.entries(currentOpponent.loot).forEach(([item, amount]) => {
player.inventory[item] = (player.inventory[item] || 0) + amount;
combatLog.textContent += `\nVous trouvez ${amount} ${item}.`;
});
const opponentTile = mapData[player.y][player.x];
if (currentOpponent.isAnimal) {
opponentTile.animals.shift();
} else {
opponentTile.monsters.shift();
}
} else {
combatLog.textContent = `Vous avez été vaincu...`;
player.derivedStats.currentHp = 1;
}
currentOpponent = null;
setTimeout(() => {
document.getElementById('combat-screen').classList.add('hidden');
document.getElementById('combat-screen').classList.remove('flex');
gameState = 'exploring';
updateAllInfoPanels();
}, 2000);
}
updateCombatUI() {
document.getElementById('combat-player-hp-text').textContent = `${this.player.creature.hp} / ${player.derivedStats.maxHp}`;
document.getElementById('combat-player-hp-bar').style.width = `${(this.player.creature.hp / player.derivedStats.maxHp) * 100}%`;
if (this.opponent) {
document.getElementById('combat-opponent-name').textContent = this.opponent.name;
document.getElementById('combat-opponent-icon').textContent = currentOpponent.icon;
document.getElementById('combat-opponent-hp-text').textContent = `${this.opponent.currentHp} / ${this.opponent.hp}`;
document.getElementById('combat-opponent-hp-bar').style.width = `${(this.opponent.currentHp / this.opponent.hp) * 100}%`;
}
}
}
class Quest {
constructor( ) {
this.id = null;
this.definition = null;
this.currentStage = 0;
this.status = 'inactive'; // inactive, active, completed
}
getCurrentStage() {
return this.definition.stages[this.currentStage];
}
advance(choiceIndex) {
const stage = this.getCurrentStage();
if(stage.choices && stage.choices[choiceIndex]) {
const nextStageIndex = stage.choices[choiceIndex].nextStage;
if (nextStageIndex !== null) {
this.currentStage = nextStageIndex;
if(this.status === 'inactive') this.status = 'active';
if(this.getCurrentStage().isEnd) {
this.status = 'completed';
}
}
}
}
async setQuest(npc) {
if(npc.questId && this.quests[npc.questId]) {
return this.quests[npc.questId];
}
try {
const questDefinition = await this.generateQuestForNpc(npc);
if(questDefinition) {
const questId = `GEMINI_${Date.now()}`;
npc.assignQuest(questId);
this.id=questId;
this.definition=questDefinition;
}
} catch(e) {
console.error("Gemini quest generation failed, using fallback.", e);
const fallbackId = 'WOLF_MENACE';
npc.assignQuest(fallbackId);
this.id=fallbackId;
this.definition=QUEST_DATABASE[fallbackId];
}
return null;
}
async generateQuestForNpc(npc) {
if (!geminiApiKey) {
throw new Error("API key is missing.");
}
const prompt = `Génère une quête simple pour un jeu RPG en 2D isométrique.
Le PNJ est un "${npc.type.name}".
Il se trouve près d'une structure de type "${npc.homeTile.structure.type.name}" dans un biome de type "${npc.homeTile.biome.name}".
Crée une quête avec un titre, et 3 à 4 étapes.
La première étape (index 0) doit proposer d'accepter ou de refuser la quête.
L'étape suivante (index 1) doit décrire l'objectif. L'objectif doit être simple, comme tuer un certain nombre de monstres ou collecter des objets.
La dernière étape doit être la récompense (isEnd: true).
Réponds UNIQUEMENT avec un objet JSON valide qui suit le schéma ci-dessous. N'ajoute aucun texte avant ou après le JSON.
`;
const payload = {
contents: [{ parts: [{ text: prompt }] }],
generationConfig: {
responseMimeType: "application/json",
responseSchema: {
type: "OBJECT",
properties: {
"title": { "type": "STRING" },
"stages": {
"type": "ARRAY",
"items": {
"type": "OBJECT",
"properties": {
"text": { "type": "STRING" },
"choices": {
"type": "ARRAY",
"items": {
"type": "OBJECT",
"properties": {
"text": { "type": "STRING" },
"nextStage": { "type": ["NUMBER", "NULL"] }
},
"required": ["text"]
}
},
"objective": { "type": ["OBJECT", "NULL"], "properties": {"type": {"type": "STRING"}, "target": {"type": "STRING"}, "count": {"type": "NUMBER"}} },
"reward": { "type": ["OBJECT", "NULL"], "properties": {"xp": {"type": "NUMBER"}, "items": {"type": "ARRAY", "items": { "type": "STRING" }}} },
"isEnd": { "type": ["BOOLEAN", "NULL"] }
},
"required": ["text", "choices"]
}
}
},
"required": ["title", "stages"]
}
}
};
const apiUrl = `https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash-preview-05-20:generateContent?key=${geminiApiKey}`;
const response = await fetch(apiUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
});
if (!response.ok) {
const errorBody = await response.text();
console.error("API Error Body:", errorBody);
throw new Error(`API call failed with status: ${response.status}`);
}
const result = await response.json();
const jsonText = result.candidates?.[0]?.content?.parts?.[0]?.text;
if(jsonText) {
return JSON.parse(jsonText);
}
throw new Error("Invalid response from API.");
}
}
class World {
constructor(name) {
this.name = name;
this.map = new Map(200);
this.currentTime=new Time();
this.animals = [];
this.npcs = [];
this.camera = new Camera(0,0, canvas);
this.player=null;
}
setDesign(){
ctx.fillStyle = '#000000';
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.save();
ctx.translate(this.camera.x, this.camera.y);
const [startX, endX, startY, endY] = this.getVisibleTileBounds();
for (let y = startY; y < endY; y++) {
for (let x = startX; x < endX; x++) {
if (this.world.map.tiles[y] && this.world.map.tiles[y][x]) {
this.world.map.tiles[y][x].setDesign(ctx);
}
}
}
//this.world.animals.forEach(animal => animal.setDesign(ctx));
//this.world.npcs.forEach(npc => npc.setDesign(ctx));
this.player.setDesign(ctx);
ctx.restore();
ctx.save();
this.currentTime.setDesign(ctx);
ctx.restore();
}
getVisibleTileBounds() {
const margin = 5;
const viewWidth = canvas.width;
const viewHeight = canvas.height;
const isoToCart = (isoX, isoY) => {
const cartX = (isoX / (TILE_WIDTH / 2) + isoY / (TILE_HEIGHT / 2)) / 2;
const cartY = (isoY / (TILE_HEIGHT / 2) - isoX / (TILE_WIDTH / 2)) / 2;
return { x: Math.floor(cartX), y: Math.floor(cartY) };
};
const topLeft = isoToCart(-this.camera.x, -this.camera.y);
const bottomRight = isoToCart(-this.camera.x + viewWidth, -this.camera.y + viewHeight);
const startX = Math.max(0, topLeft.x - margin);
const endX = Math.min(this.map.size - 1, bottomRight.x + margin);
const startY = Math.max(0, topLeft.y - margin);
const endY = Math.min(this.map.size - 1, bottomRight.y + margin);
return [startX, endX, startY, endY];
}
update(currentTime) {
this.updateVisibility(this.player.creature.position);
// a changer!
//this.world.animals.forEach(animal => animal.update(currentTime, this.world.map));
//this.world.npcs.forEach(npc => npc.update(currentTime, this.world.map));
this.camera.setCamera(this.player.creature.position);
// Update all biome sprites
for (const key in BIOME_TYPE) {
if (BIOME_TYPE[key].sprite) {
BIOME_TYPE[key].sprite.update(currentTime);
}
}
this.currentTime.tick();
}
updateVisibility(position) {
const px = position.x;
const py = position.y;
const radius = VISION_RADIUS;
const radiusSq = radius * radius;
const viewBox = {
minX: Math.max(0, px - radius - 2),
maxX: Math.min(this.map.size - 1, px + radius + 2),
minY: Math.max(0, py - radius - 2),
maxY: Math.min(this.map.size - 1, py + radius + 2)
};
for(let y = viewBox.minY; y <= viewBox.maxY; y++) {
for(let x = viewBox.minX; x <= viewBox.maxX; x++) {
const tile = this.map.tiles[y][x];
if(tile.visibility === 2) tile.visibility = 1;
const dx = px - x;
const dy = py - y;
if (dx * dx + dy * dy <= radiusSq) {
tile.visibility = 2;
}
}
}
}
}
class Camera {
constructor(x=0,y=0,canvas) {
@ -682,74 +1002,25 @@ document.addEventListener('DOMContentLoaded', () => {
constructor() {
this.world = new World('Defiance');
this.camera = new Camera(0,0, canvas);
this.setControls();
this.loop = this.loop.bind(this);
this.player=null;
this.setInitial();
}
loop(currentTime) {
this.update(currentTime);
this.setDesign();
this.handleMovement(currentTime);
this.world.update(currentTime);
this.world.setDesign();
requestAnimationFrame(this.loop);
}
update(currentTime) {
this.handleMovement(currentTime);
this.updateVisibility();
// a changer!
this.world.animals.forEach(animal => animal.update(currentTime, this.world.map));
this.world.npcs.forEach(npc => npc.update(currentTime, this.world.map));
this.camera.setCamera(this.player.creature.position);
// Update all biome sprites
for (const key in Biome) {
if (Biome[key].sprite) {
Biome[key].sprite.update(currentTime);
}
}
this.world.currentTime.tick();
}
updateVisibility() {
const map = this.world.map;
const px = this.player.creature.position.x;
const py = this.player.creature.position.y;
const radius = VISION_RADIUS;
const radiusSq = radius * radius;
const viewBox = {
minX: Math.max(0, px - radius - 2),
maxX: Math.min(map.size - 1, px + radius + 2),
minY: Math.max(0, py - radius - 2),
maxY: Math.min(map.size - 1, py + radius + 2)
};
for(let y = viewBox.minY; y <= viewBox.maxY; y++) {
for(let x = viewBox.minX; x <= viewBox.maxX; x++) {
const tile = map.tiles[y][x];
if(tile.visibility === 2) tile.visibility = 1;
const dx = px - x;
const dy = py - y;
if (dx * dx + dy * dy <= radiusSq) {
tile.visibility = 2;
}
}
}
}
setInitial(){
let spawnX, spawnY;
do {
spawnX = Math.floor(Math.random() * this.world.map.size);
spawnY = Math.floor(Math.random() * this.world.map.size);
} while (!this.world.map.tiles[spawnY][spawnX].biome || !this.world.map.tiles[spawnY][spawnX].biome.movements.includes('walk') );
this.player = new Player(new Creature('player',new Attributes(RACE.HUMAN),1,null,null,this.world.map.tiles[spawnY][spawnX].position,'human',RACE.HUMAN,10,null),);
this.updateVisibility();
} while (!this.world.map.tiles[spawnY][spawnX].biome || !this.world.map.tiles[spawnY][spawnX].biome.type.movements.includes('walk') );
this.world.player = new Player(new Creature('player',new Attributes(RACE.HUMAN),1,null,null,this.world.map.tiles[spawnY][spawnX].position,'human',RACE.HUMAN,10,null),);
this.world.updateVisibility(this.world.map.tiles[spawnY][spawnX].position);
}
setControls() {
document.addEventListener('keydown', e => {
@ -775,71 +1046,22 @@ document.addEventListener('DOMContentLoaded', () => {
btn.addEventListener('touchend', e => { e.preventDefault(); controls[dir] = false; });
}
}
setDesign(){
ctx.fillStyle = '#000000';
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.save();
ctx.translate(this.camera.x, this.camera.y);
const [startX, endX, startY, endY] = this.getVisibleTileBounds();
for (let y = startY; y < endY; y++) {
for (let x = startX; x < endX; x++) {
if (this.world.map.tiles[y] && this.world.map.tiles[y][x]) {
this.world.map.tiles[y][x].setDesign(ctx);
}
}
}
this.world.animals.forEach(animal => animal.setDesign(ctx));
this.world.npcs.forEach(npc => npc.setDesign(ctx));
this.player.setDesign(ctx);
ctx.restore();
ctx.save();
this.world.currentTime.setDesign(ctx);
ctx.restore();
}
getVisibleTileBounds() {
const margin = 5;
const viewWidth = canvas.width;
const viewHeight = canvas.height;
const isoToCart = (isoX, isoY) => {
const cartX = (isoX / (TILE_WIDTH / 2) + isoY / (TILE_HEIGHT / 2)) / 2;
const cartY = (isoY / (TILE_HEIGHT / 2) - isoX / (TILE_WIDTH / 2)) / 2;
return { x: Math.floor(cartX), y: Math.floor(cartY) };
};
const topLeft = isoToCart(-this.camera.x, -this.camera.y);
const bottomRight = isoToCart(-this.camera.x + viewWidth, -this.camera.y + viewHeight);
const startX = Math.max(0, topLeft.x - margin);
const endX = Math.min(this.world.map.size - 1, bottomRight.x + margin);
const startY = Math.max(0, topLeft.y - margin);
const endY = Math.min(this.world.map.size - 1, bottomRight.y + margin);
return [startX, endX, startY, endY];
}
handleMovement(currentTime) {
if (currentTime - lastMoveTime < 150) return;
let moved = false;
if (controls.up) { this.player.move(0, -1,this.world.map); moved = true; }
if (controls.down) { this.player.move(0, 1,this.world.map); moved = true; }
if (controls.left) { this.player.move(-1, 0,this.world.map); moved = true; }
if (controls.right) { this.player.move(1, 0,this.world.map); moved = true; }
if (controls.up) { this.world.player.move(0, -1,this.world.map); moved = true; }
if (controls.down) { this.world.player.move(0, 1,this.world.map); moved = true; }
if (controls.left) { this.world.player.move(-1, 0,this.world.map); moved = true; }
if (controls.right) { this.world.player.move(1, 0,this.world.map); moved = true; }
if(moved) lastMoveTime = currentTime;
}
handleInteraction() {
if (!controls.interact) return;
controls.interact = false; // Consume the action
const px = this.player.creature.position.x;
const py = this.player.creature.position.y;
const px = this.world.player.creature.position.x;
const py = this.world.player.creature.position.y;
let targetNpc = null;
for (const npc of this.world.npcs) {
@ -1048,8 +1270,8 @@ document.addEventListener('DOMContentLoaded', () => {
canvas.height = canvas.parentElement.clientHeight;
try {
for (const key in Biome) {
if (Biome[key].design) { Biome[key].sprite = new Sprite(Biome[key]);}
for (const key in BIOME_TYPE) {
if (BIOME_TYPE[key].design) { BIOME_TYPE[key].sprite = new Sprite(BIOME_TYPE[key]);}
}
const assetIds = ['forest-svg', 'village-svg', 'city-svg', 'player-svg', 'enchanted-forest-svg', 'swamp-svg', 'wolf-pack-svg', 'boar-svg', 'bird-svg', 'farm-svg', 'camp-svg', 'npc-svg','house-svg','cult-svg','market-svg','mine-svg'];