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', () => { 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_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 }, 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 }, 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 = { 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 } }, 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.FOREST, Biome.GRASSLAND], spawnChance: 0.02, size: { w: 30, h: 30 }, offset: { x: -15, y: -28 } }, 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.FOREST, Biome.GRASSLAND, Biome.MOUNTAIN, Biome.BEACH], spawnChance: 0.03, size: { w: 15, h: 12 }, offset: { x: -22, y: -45 }, flightHeight: 40 }, 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 --- // --- Global Asset Variables ---
@ -322,49 +322,35 @@ document.addEventListener('DOMContentLoaded', () => {
ctx.restore(); ctx.restore();
} }
} }
class Biome {
class Tile { constructor() {
this.type=null;
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();
} }
setType(position){
setBiome() {
let scale = 0.05; let scale = 0.05;
let eRaw = (simplex.noise2D(this.position.x * scale, this.position.y * scale) + 1) / 2; let eRaw = (simplex.noise2D(position.x * scale, position.y * scale) + 1) / 2;
let tRaw = (simplex.noise2D(this.position.x * scale * 0.8, this.position.y * scale * 0.8) + 1) / 2; let tRaw = (simplex.noise2D(position.x * scale * 0.8, 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; let mRaw = (simplex.noise2D(position.x * scale * 1.5, position.y * scale * 1.5) + 1) / 2;
if (eRaw < 0.25) this.biome = Biome.WATER_DEEP; if (eRaw < 0.25) this.type = BIOME_TYPE.WATER_DEEP;
else if (eRaw < 0.3) this.biome = Biome.WATER_SHALLOW; else if (eRaw < 0.3) this.type = BIOME_TYPE.WATER_SHALLOW;
else if (eRaw < 0.32) this.biome = Biome.SWAMP; else if (eRaw < 0.32) this.type = BIOME_TYPE.SWAMP;
else if (eRaw < 0.35) this.biome = Biome.BEACH; else if (eRaw < 0.35) this.type = BIOME_TYPE.BEACH;
else if (eRaw > 0.85) this.biome = Biome.SNOWMOUNTAIN; else if (eRaw > 0.85) this.type = BIOME_TYPE.SNOWMOUNTAIN;
else if (eRaw > 0.75) this.biome = Biome.MOUNTAIN; else if (eRaw > 0.75) this.type = BIOME_TYPE.MOUNTAIN;
else { else {
if (tRaw < 0.3) this.biome = Biome.DESERT; if (tRaw < 0.3) this.type = BIOME_TYPE.DESERT;
else if (tRaw > 0.6) this.biome = (mRaw > 0.7) ? Biome.ENCHANTED_FOREST : Biome.FOREST; else if (tRaw > 0.6) this.type = (mRaw > 0.7) ? BIOME_TYPE.ENCHANTED_FOREST : BIOME_TYPE.FOREST;
else this.biome = Biome.GRASSLAND; else this.type = BIOME_TYPE.GRASSLAND;
} }
} };
setDesign(ctx, position){
setDesign(ctx){ const screenPos = position.cartToIso();
if (!this.biome || this.visibility === 0) return; const elevationHeight = position.h * ELEVATION_STEP;
const screenPos = this.position.cartToIso();
const elevationHeight = this.position.h * ELEVATION_STEP;
ctx.save(); ctx.save();
ctx.translate(screenPos.x, screenPos.y - elevationHeight); ctx.translate(screenPos.x, screenPos.y - elevationHeight);
const baseColor = this.biome.summerColor; const baseColor = this.type.summerColor;
const shadowColor = shadeColor(baseColor, -30); const shadowColor = shadeColor(baseColor, -30);
// Draw tile faces // Draw tile faces
@ -390,20 +376,53 @@ document.addEventListener('DOMContentLoaded', () => {
ctx.closePath(); ctx.closePath();
ctx.fill(); 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 // Draw features if fully visible
if(this.visibility === 2) { 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 ) { if (this.structure ) {
this.structure.setDesign(ctx); 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 // Draw fog overlay if seen but not currently visible
@ -448,7 +467,7 @@ document.addEventListener('DOMContentLoaded', () => {
const animalType = ANIMAL_TYPES[key]; const animalType = ANIMAL_TYPES[key];
if (animalType.biomes.includes(this.biome) && Math.random() < animalType.spawnChance) { if (animalType.biomes.includes(this.biome) && Math.random() < animalType.spawnChance) {
if( this.biome.movements.includes(animalType.movement)) { 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; const newY = this.settlement.position.y + y;
if (newX >= 0 && newX < gameMap.size && newY >= 0 && newY < gameMap.size) { if (newX >= 0 && newX < gameMap.size && newY >= 0 && newY < gameMap.size) {
const targetTile = gameMap.tiles[newY][newX]; 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); possibleMoves.push(targetTile);
} }
} }
@ -625,7 +644,7 @@ document.addEventListener('DOMContentLoaded', () => {
this.creature=creature; this.creature=creature;
this.equipments=equipments; this.equipments=equipments;
} }
setDesign(ctx) { setDesign(ctx) {
const position = this.creature.position const position = this.creature.position
if(!position) return; if(!position) return;
@ -648,22 +667,323 @@ document.addEventListener('DOMContentLoaded', () => {
const newY = this.creature.position.y + dy; const newY = this.creature.position.y + dy;
if (newX >= 0 && newX < gameMap.size && newY >= 0 && newY < gameMap.size) { if (newX >= 0 && newX < gameMap.size && newY >= 0 && newY < gameMap.size) {
const targetTile = gameMap.tiles[newY][newX].position; 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; 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 { class World {
constructor(name) { constructor(name) {
this.name = name; this.name = name;
this.map = new Map(200); this.map = new Map(200);
this.currentTime=new Time(); this.currentTime=new Time();
this.animals = []; this.camera = new Camera(0,0, canvas);
this.npcs = []; 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 { class Camera {
constructor(x=0,y=0,canvas) { constructor(x=0,y=0,canvas) {
@ -682,74 +1002,25 @@ document.addEventListener('DOMContentLoaded', () => {
constructor() { constructor() {
this.world = new World('Defiance'); this.world = new World('Defiance');
this.camera = new Camera(0,0, canvas);
this.setControls(); this.setControls();
this.loop = this.loop.bind(this); this.loop = this.loop.bind(this);
this.player=null;
this.setInitial(); this.setInitial();
} }
loop(currentTime) { loop(currentTime) {
this.update(currentTime); this.handleMovement(currentTime);
this.setDesign(); this.world.update(currentTime);
this.world.setDesign();
requestAnimationFrame(this.loop); 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(){ setInitial(){
let spawnX, spawnY; let spawnX, spawnY;
do { do {
spawnX = Math.floor(Math.random() * this.world.map.size); spawnX = Math.floor(Math.random() * this.world.map.size);
spawnY = 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') ); } while (!this.world.map.tiles[spawnY][spawnX].biome || !this.world.map.tiles[spawnY][spawnX].biome.type.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.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.updateVisibility(); this.world.updateVisibility(this.world.map.tiles[spawnY][spawnX].position);
} }
setControls() { setControls() {
document.addEventListener('keydown', e => { document.addEventListener('keydown', e => {
@ -775,71 +1046,22 @@ document.addEventListener('DOMContentLoaded', () => {
btn.addEventListener('touchend', e => { e.preventDefault(); controls[dir] = false; }); 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) { handleMovement(currentTime) {
if (currentTime - lastMoveTime < 150) return; if (currentTime - lastMoveTime < 150) return;
let moved = false; let moved = false;
if (controls.up) { this.player.move(0, -1,this.world.map); moved = true; } if (controls.up) { this.world.player.move(0, -1,this.world.map); moved = true; }
if (controls.down) { this.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.player.move(-1, 0,this.world.map); moved = true; } if (controls.left) { this.world.player.move(-1, 0,this.world.map); moved = true; }
if (controls.right) { this.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; if(moved) lastMoveTime = currentTime;
} }
handleInteraction() { handleInteraction() {
if (!controls.interact) return; if (!controls.interact) return;
controls.interact = false; // Consume the action controls.interact = false; // Consume the action
const px = this.player.creature.position.x; const px = this.world.player.creature.position.x;
const py = this.player.creature.position.y; const py = this.world.player.creature.position.y;
let targetNpc = null; let targetNpc = null;
for (const npc of this.world.npcs) { for (const npc of this.world.npcs) {
@ -1048,8 +1270,8 @@ document.addEventListener('DOMContentLoaded', () => {
canvas.height = canvas.parentElement.clientHeight; canvas.height = canvas.parentElement.clientHeight;
try { try {
for (const key in Biome) { for (const key in BIOME_TYPE) {
if (Biome[key].design) { Biome[key].sprite = new Sprite(Biome[key]);} 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']; 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'];