🤖 AI Behavior System + Sound Manifest
✅ NEW SYSTEMS: 1. AnimalBehavior.js - Wander, Flee, Follow, Glowing Eyes 2. NPCIdleBehavior.js - Idle animations (hat fix, sweat wipe) 3. ANIMAL_SOUND_MANIFEST.md - 40+ sound mappings 🎮 AI FEATURES: - Smooth movement (not grid-based) ✓ - Flee from player (100px range) ✓ - Follow with delay (cargo animals: llama, horse, donkey) ✓ - Glowing eyes in darkness (NOIR EFFECT!) ✓ - Neon pink/green eyes visible at distance - Perfect for 'eyes in forest' atmosphere 👤 NPC FEATURES: - Random idle animations every 3-8 seconds - Fix hat, wipe sweat, look around, scratch head - Gronk-specific: coin counting, item polishing 🔊 SOUND SYSTEM: - Kenney pack mapping for 23 animal sounds - Footstep variations (mud, grass, cobble) - Noir sound design: muffled distant sounds + reverb DEMO: 56/60 sprites (93%)! Next: 4 remaining sprites + integration testing
This commit is contained in:
158
ANIMAL_SOUND_MANIFEST.md
Normal file
158
ANIMAL_SOUND_MANIFEST.md
Normal file
@@ -0,0 +1,158 @@
|
||||
# 🔊 **ANIMAL & NPC SOUND MANIFEST**
|
||||
**Krvava Žetev - Gothic Farm Game**
|
||||
|
||||
**Source:** Kenney Sound Packs (already downloaded)
|
||||
**Location:** `/assets/audio/sfx/`
|
||||
|
||||
---
|
||||
|
||||
## 🐮 **FARM ANIMAL SOUNDS**
|
||||
|
||||
### **Required Sounds (23 total):**
|
||||
|
||||
**COW:**
|
||||
- `cow_idle.wav` - Low moo, ambient
|
||||
- `cow_flee.wav` - Alarmed moo, louder
|
||||
- `cow_footstep.wav` - Heavy hoof step
|
||||
|
||||
**PIG:**
|
||||
- `pig_idle.wav` - Snort, oink
|
||||
- `pig_flee.wav` - Squealing
|
||||
- `pig_footstep.wav` - Medium step
|
||||
|
||||
**SHEEP:**
|
||||
- `sheep_idle.wav` - Baa sound
|
||||
- `sheep_flee.wav` - Panicked baa
|
||||
- `sheep_footstep.wav` - Light hoof step
|
||||
|
||||
**CHICKEN:**
|
||||
- `chicken_idle.wav` - Cluck
|
||||
- `chicken_flee.wav` - Fast clucking/cackle
|
||||
- `chicken_footstep.wav` - Light scratch
|
||||
|
||||
**DUCK:**
|
||||
- `duck_idle.wav` - Quack
|
||||
- `duck_flee.wav` - Fast quacking
|
||||
- `duck_footstep.wav` - Paddle sound
|
||||
|
||||
**GOAT:**
|
||||
- `goat_idle.wav` - Maa/bleat
|
||||
- `goat_flee.wav` - Loud bleat
|
||||
- `goat_footstep.wav` - Hoof clatter
|
||||
|
||||
**HORSE:**
|
||||
- `horse_idle.wav` - Neigh/whinny
|
||||
- `horse_flee.wav` - Gallop neigh
|
||||
- `horse_footstep.wav` - Heavy gallop
|
||||
|
||||
**RABBIT:**
|
||||
- `rabbit_idle.wav` - Light chitter
|
||||
- `rabbit_flee.wav` - Quick squeak
|
||||
- `rabbit_footstep.wav` - Soft hop
|
||||
|
||||
---
|
||||
|
||||
## 👤 **NPC AMBIENT SOUNDS**
|
||||
|
||||
**GRONK (Shop):**
|
||||
- `gronk_idle_breath.wav` - Heavy breathing
|
||||
- `gronk_coins.wav` - Coin counting
|
||||
- `gronk_grumble.wav` - Low grumble
|
||||
|
||||
**GENERIC NPC:**
|
||||
- `npc_footstep_dirt.wav` - Walking on dirt
|
||||
- `npc_footstep_cobble.wav` - Walking on cobblestone
|
||||
- `npc_clothing_rustle.wav` - Moving
|
||||
|
||||
---
|
||||
|
||||
## 🌾 **ENVIRONMENTAL SOUNDS (Related)**
|
||||
|
||||
**MUD & TERRAIN:**
|
||||
- `footstep_mud_1.wav` - Squelch
|
||||
- `footstep_mud_2.wav` - Squelch variant
|
||||
- `footstep_grass.wav` - Soft grass step
|
||||
- `footstep_cobble.wav` - Hard stone step
|
||||
|
||||
**FARM AMBIENCE:**
|
||||
- `farm_wind.wav` - Light wind
|
||||
- `farm_distant_animals.wav` - Mixed distant sounds
|
||||
- `gate_creak.wav` - Gate opening/closing
|
||||
- `chain_rattle.wav` - Metal chains (locked gate)
|
||||
|
||||
---
|
||||
|
||||
## 📂 **KENNEY PACK MAPPING**
|
||||
|
||||
**Where to find these in your Kenney downloads:**
|
||||
|
||||
**Animal Sounds:** `Kenney/Voice-Over Pack/`
|
||||
- Look for: animal, creature sounds
|
||||
- Variants: low, high pitch versions
|
||||
|
||||
**Footsteps:** `Kenney/Impact Sounds/`
|
||||
- Look for: step, walk, shuffle
|
||||
- Different surfaces: grass, stone, mud
|
||||
|
||||
**Ambient:** `Kenney/RPG Audio/`
|
||||
- Environment loops
|
||||
- Object interactions
|
||||
|
||||
**UI/Interact:** `Kenney/Interface Sounds/`
|
||||
- For shop interactions
|
||||
|
||||
---
|
||||
|
||||
## 🎮 **IMPLEMENTATION CHECKLIST**
|
||||
|
||||
**Phase 1 - Priority (10 sounds):**
|
||||
- [ ] `cow_idle.wav`
|
||||
- [ ] `cow_flee.wav`
|
||||
- [ ] `pig_idle.wav`
|
||||
- [ ] `sheep_idle.wav`
|
||||
- [ ] `chicken_idle.wav`
|
||||
- [ ] `horse_idle.wav`
|
||||
- [ ] `footstep_mud_1.wav`
|
||||
- [ ] `footstep_grass.wav`
|
||||
- [ ] `npc_footstep_dirt.wav`
|
||||
- [ ] `gronk_grumble.wav`
|
||||
|
||||
**Phase 2 - Full Set (All 40+ sounds)**
|
||||
|
||||
---
|
||||
|
||||
## 🔧 **PROCESSING NOTES**
|
||||
|
||||
**Format:** Convert all to `.wav` or `.ogg`
|
||||
**Sample Rate:** 44.1kHz
|
||||
**Channels:** Mono (stereo for ambience)
|
||||
**Normalization:** -3dB peak
|
||||
|
||||
**Volume Levels:**
|
||||
- Idle sounds: 70%
|
||||
- Flee sounds: 90%
|
||||
- Footsteps: 50%
|
||||
- Ambience: 40%
|
||||
|
||||
---
|
||||
|
||||
## 💡 **NOIR SOUND DESIGN TWIST**
|
||||
|
||||
**For glowing-eye effect in darkness:**
|
||||
|
||||
When animal is in shadow/distant:
|
||||
1. Mute normal animal sounds
|
||||
2. Play low-pass filtered version (muffled)
|
||||
3. Add eerie reverb/echo
|
||||
4. Optional: Add faint heartbeat or breathing
|
||||
|
||||
**When Kai's flashlight shines on them:**
|
||||
1. Sharp sound cue (surprise!)
|
||||
2. Immediate flee sound trigger
|
||||
3. Rapid footstep sequence
|
||||
|
||||
This makes the "glowing eyes in forest" moment EXTRA creepy! 👀✨
|
||||
|
||||
---
|
||||
|
||||
**Start with Phase 1 (10 sounds), test, then expand!**
|
||||
@@ -1,10 +1,10 @@
|
||||
# 📋 **DEMO PRODUCTION TASKS - COMPLETE CHECKLIST**
|
||||
|
||||
**Created:** Jan 8, 2026 21:37 CET
|
||||
**Updated:** Jan 8, 2026 23:21 CET
|
||||
**Updated:** Jan 8, 2026 23:31 CET
|
||||
**Purpose:** ALL remaining demo assets as tasks
|
||||
**Target:** 60 sprites total (5 house + 7 barn + 48 remaining)
|
||||
**Status:** 40/60 complete (67%) ✨
|
||||
**Target:** 60 sprites total
|
||||
**Status:** 56/60 complete (93%) 🎉
|
||||
|
||||
---
|
||||
|
||||
|
||||
278
src/ai/AnimalBehavior.js
Normal file
278
src/ai/AnimalBehavior.js
Normal file
@@ -0,0 +1,278 @@
|
||||
/**
|
||||
* 🐮 ANIMAL AI BEHAVIOR SYSTEM
|
||||
* "Krvava Žetev" - Gothic Farm Game
|
||||
*
|
||||
* Behaviors:
|
||||
* - Wander: Random smooth movement (not grid-based)
|
||||
* - Flee: Run away from player when close
|
||||
* - Glowing Eyes: Neon eyes visible in darkness (noir effect!)
|
||||
* - Follow: Cargo animals (llama, horse) follow with delay
|
||||
*/
|
||||
|
||||
export class AnimalBehavior {
|
||||
constructor(scene, sprite, animalType) {
|
||||
this.scene = scene;
|
||||
this.sprite = sprite;
|
||||
this.animalType = animalType;
|
||||
|
||||
// Behavior states
|
||||
this.state = 'wander'; // 'wander', 'flee', 'follow', 'idle'
|
||||
this.wanderTimer = 0;
|
||||
this.wanderDuration = Phaser.Math.Between(2000, 5000);
|
||||
this.wanderAngle = Math.random() * Math.PI * 2;
|
||||
|
||||
// Movement params
|
||||
this.speed = this.getSpeed();
|
||||
this.fleeDistance = 100; // pixels
|
||||
this.fleeSpeed = this.speed * 2;
|
||||
|
||||
// Glowing eyes effect (NOIR!)
|
||||
this.glowingEyes = null;
|
||||
this.eyeColor = this.getEyeColor();
|
||||
this.createGlowingEyes();
|
||||
|
||||
// Cargo follow system (for llama, horse, donkey)
|
||||
this.isCargoAnimal = ['llama', 'horse', 'donkey'].includes(animalType);
|
||||
this.followTarget = null;
|
||||
this.followDelay = 0.5; // seconds
|
||||
this.followHistory = [];
|
||||
}
|
||||
|
||||
getSpeed() {
|
||||
const speeds = {
|
||||
cow: 30,
|
||||
pig: 35,
|
||||
sheep: 25,
|
||||
chicken: 50,
|
||||
duck: 45,
|
||||
goat: 40,
|
||||
horse: 60,
|
||||
rabbit: 70,
|
||||
donkey: 35,
|
||||
llama: 40
|
||||
};
|
||||
return speeds[this.animalType] || 30;
|
||||
}
|
||||
|
||||
getEyeColor() {
|
||||
// Noir glowing eyes - pink or green like Kai's dreads!
|
||||
const colors = {
|
||||
cow: 0xff0066, // neon pink
|
||||
pig: 0xff0066, // neon pink
|
||||
sheep: 0x00ff88, // neon green
|
||||
chicken: 0xffff00, // yellow
|
||||
duck: 0x00ffff, // cyan
|
||||
goat: 0xff0066, // neon pink
|
||||
horse: 0x00ff88, // neon green
|
||||
rabbit: 0xff00ff, // purple
|
||||
donkey: 0x00ff88, // neon green
|
||||
llama: 0xff0066 // neon pink
|
||||
};
|
||||
return colors[this.animalType] || 0xff0066;
|
||||
}
|
||||
|
||||
createGlowingEyes() {
|
||||
// Create glowing eye sprites for noir darkness effect
|
||||
this.glowingEyes = this.scene.add.container(this.sprite.x, this.sprite.y);
|
||||
|
||||
// Two small glowing circles for eyes
|
||||
const leftEye = this.scene.add.circle(-8, -10, 3, this.eyeColor);
|
||||
const rightEye = this.scene.add.circle(8, -10, 3, this.eyeColor);
|
||||
|
||||
// Add glow effect
|
||||
leftEye.setAlpha(0.8);
|
||||
rightEye.setAlpha(0.8);
|
||||
|
||||
this.glowingEyes.add([leftEye, rightEye]);
|
||||
|
||||
// Hide by default, show only in darkness or at distance
|
||||
this.glowingEyes.setVisible(false);
|
||||
this.glowingEyes.setDepth(this.sprite.depth + 1);
|
||||
}
|
||||
|
||||
update(time, delta) {
|
||||
if (!this.sprite.active) return;
|
||||
|
||||
// Update glowing eyes position
|
||||
if (this.glowingEyes) {
|
||||
this.glowingEyes.setPosition(this.sprite.x, this.sprite.y);
|
||||
this.updateGlowingEyesVisibility();
|
||||
}
|
||||
|
||||
// Behavior state machine
|
||||
switch (this.state) {
|
||||
case 'wander':
|
||||
this.updateWander(time, delta);
|
||||
break;
|
||||
case 'flee':
|
||||
this.updateFlee(time, delta);
|
||||
break;
|
||||
case 'follow':
|
||||
this.updateFollow(time, delta);
|
||||
break;
|
||||
case 'idle':
|
||||
this.updateIdle(time, delta);
|
||||
break;
|
||||
}
|
||||
|
||||
// Check for player proximity (flee trigger)
|
||||
this.checkPlayerProximity();
|
||||
}
|
||||
|
||||
updateWander(time, delta) {
|
||||
this.wanderTimer += delta;
|
||||
|
||||
// Change direction periodically
|
||||
if (this.wanderTimer >= this.wanderDuration) {
|
||||
this.wanderTimer = 0;
|
||||
this.wanderDuration = Phaser.Math.Between(2000, 5000);
|
||||
this.wanderAngle = Math.random() * Math.PI * 2;
|
||||
|
||||
// Sometimes stop and idle
|
||||
if (Math.random() < 0.3) {
|
||||
this.state = 'idle';
|
||||
this.sprite.setVelocity(0, 0);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Smooth movement in wander direction
|
||||
const vx = Math.cos(this.wanderAngle) * this.speed;
|
||||
const vy = Math.sin(this.wanderAngle) * this.speed;
|
||||
this.sprite.setVelocity(vx, vy);
|
||||
}
|
||||
|
||||
updateFlee(time, delta) {
|
||||
const player = this.scene.player;
|
||||
if (!player) return;
|
||||
|
||||
// Flee away from player
|
||||
const angle = Phaser.Math.Angle.Between(
|
||||
player.x, player.y,
|
||||
this.sprite.x, this.sprite.y
|
||||
);
|
||||
|
||||
const vx = Math.cos(angle) * this.fleeSpeed;
|
||||
const vy = Math.sin(angle) * this.fleeSpeed;
|
||||
this.sprite.setVelocity(vx, vy);
|
||||
|
||||
// Return to wander if far enough
|
||||
const dist = Phaser.Math.Distance.Between(
|
||||
player.x, player.y,
|
||||
this.sprite.x, this.sprite.y
|
||||
);
|
||||
|
||||
if (dist > this.fleeDistance * 2) {
|
||||
this.state = 'wander';
|
||||
}
|
||||
}
|
||||
|
||||
updateFollow(time, delta) {
|
||||
if (!this.followTarget || !this.isCargoAnimal) return;
|
||||
|
||||
// Record follow history for delayed follow
|
||||
this.followHistory.push({
|
||||
x: this.followTarget.x,
|
||||
y: this.followTarget.y,
|
||||
time: time
|
||||
});
|
||||
|
||||
// Remove old history
|
||||
const cutoffTime = time - (this.followDelay * 1000);
|
||||
this.followHistory = this.followHistory.filter(h => h.time > cutoffTime);
|
||||
|
||||
// Follow delayed position
|
||||
if (this.followHistory.length > 0) {
|
||||
const target = this.followHistory[0];
|
||||
const angle = Phaser.Math.Angle.Between(
|
||||
this.sprite.x, this.sprite.y,
|
||||
target.x, target.y
|
||||
);
|
||||
|
||||
const dist = Phaser.Math.Distance.Between(
|
||||
this.sprite.x, this.sprite.y,
|
||||
target.x, target.y
|
||||
);
|
||||
|
||||
// Only move if far enough
|
||||
if (dist > 50) {
|
||||
const vx = Math.cos(angle) * this.speed;
|
||||
const vy = Math.sin(angle) * this.speed;
|
||||
this.sprite.setVelocity(vx, vy);
|
||||
} else {
|
||||
this.sprite.setVelocity(0, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
updateIdle(time, delta) {
|
||||
// Stop moving
|
||||
this.sprite.setVelocity(0, 0);
|
||||
|
||||
// Return to wander after random time
|
||||
if (Math.random() < 0.01) { // ~1% chance per frame
|
||||
this.state = 'wander';
|
||||
}
|
||||
}
|
||||
|
||||
checkPlayerProximity() {
|
||||
const player = this.scene.player;
|
||||
if (!player) return;
|
||||
|
||||
const dist = Phaser.Math.Distance.Between(
|
||||
player.x, player.y,
|
||||
this.sprite.x, this.sprite.y
|
||||
);
|
||||
|
||||
// Trigger flee if player too close
|
||||
if (dist < this.fleeDistance && this.state !== 'flee') {
|
||||
this.state = 'flee';
|
||||
|
||||
// Play flee sound if available
|
||||
if (this.scene.sound.get(`${this.animalType}_flee`)) {
|
||||
this.scene.sound.play(`${this.animalType}_flee`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
updateGlowingEyesVisibility() {
|
||||
if (!this.glowingEyes) return;
|
||||
|
||||
const player = this.scene.player;
|
||||
if (!player) return;
|
||||
|
||||
const dist = Phaser.Math.Distance.Between(
|
||||
player.x, player.y,
|
||||
this.sprite.x, this.sprite.y
|
||||
);
|
||||
|
||||
// Show glowing eyes in darkness or at distance
|
||||
// NOIR EFFECT: When Kai shines light in forest, just glowing eyes visible!
|
||||
const isDark = this.scene.darkness || false; // Check if darkness system active
|
||||
const isDistant = dist > 200 && dist < 400; // Medium distance
|
||||
|
||||
if (isDark || isDistant) {
|
||||
this.glowingEyes.setVisible(true);
|
||||
// Hide actual sprite if far
|
||||
if (dist > 300) {
|
||||
this.sprite.setAlpha(0.2);
|
||||
}
|
||||
} else {
|
||||
this.glowingEyes.setVisible(false);
|
||||
this.sprite.setAlpha(1.0);
|
||||
}
|
||||
}
|
||||
|
||||
setFollowTarget(target) {
|
||||
if (this.isCargoAnimal) {
|
||||
this.followTarget = target;
|
||||
this.state = 'follow';
|
||||
}
|
||||
}
|
||||
|
||||
destroy() {
|
||||
if (this.glowingEyes) {
|
||||
this.glowingEyes.destroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
153
src/ai/NPCIdleBehavior.js
Normal file
153
src/ai/NPCIdleBehavior.js
Normal file
@@ -0,0 +1,153 @@
|
||||
/**
|
||||
* 👤 NPC IDLE BEHAVIOR SYSTEM
|
||||
* "Krvava Žetev" - Gothic Farm Game
|
||||
*
|
||||
* Makes NPCs feel alive with random idle animations:
|
||||
* - Fix hat
|
||||
* - Wipe sweat
|
||||
* - Look around
|
||||
* - Scratch head
|
||||
* - Adjust clothes
|
||||
*/
|
||||
|
||||
export class NPCIdleBehavior {
|
||||
constructor(scene, sprite, npcType) {
|
||||
this.scene = scene;
|
||||
this.sprite = sprite;
|
||||
this.npcType = npcType;
|
||||
|
||||
// Idle animation timing
|
||||
this.idleTimer = 0;
|
||||
this.nextIdleTime = Phaser.Math.Between(3000, 8000);
|
||||
|
||||
// Available idle animations
|
||||
this.idleAnimations = this.getIdleAnimations();
|
||||
this.currentIdle = null;
|
||||
}
|
||||
|
||||
getIdleAnimations() {
|
||||
// Different NPCs have different idle behaviors
|
||||
const genericIdles = [
|
||||
'fix_hat',
|
||||
'wipe_sweat',
|
||||
'look_around',
|
||||
'scratch_head',
|
||||
'adjust_clothes',
|
||||
'yawn',
|
||||
'stretch'
|
||||
];
|
||||
|
||||
const npcSpecific = {
|
||||
'gronk': [...genericIdles, 'adjust_apron', 'count_coins', 'polish_item'],
|
||||
'farmer': [...genericIdles, 'lean_on_fence', 'check_watch', 'spit'],
|
||||
'merchant': [...genericIdles, 'fix_glasses', 'write_note', 'arrange_goods'],
|
||||
'default': genericIdles
|
||||
};
|
||||
|
||||
return npcSpecific[this.npcType] || npcSpecific.default;
|
||||
}
|
||||
|
||||
update(time, delta) {
|
||||
if (!this.sprite.active) return;
|
||||
|
||||
this.idleTimer += delta;
|
||||
|
||||
// Trigger random idle animation
|
||||
if (this.idleTimer >= this.nextIdleTime && !this.currentIdle) {
|
||||
this.playRandomIdle();
|
||||
this.idleTimer = 0;
|
||||
this.nextIdleTime = Phaser.Math.Between(3000, 8000);
|
||||
}
|
||||
}
|
||||
|
||||
playRandomIdle() {
|
||||
const idle = Phaser.Utils.Array.GetRandom(this.idleAnimations);
|
||||
this.currentIdle = idle;
|
||||
|
||||
// Play animation if it exists
|
||||
const animKey = `${this.npcType}_${idle}`;
|
||||
|
||||
if (this.sprite.anims && this.sprite.anims.exists(animKey)) {
|
||||
this.sprite.play(animKey);
|
||||
|
||||
// Return to default idle after animation
|
||||
this.sprite.on('animationcomplete', () => {
|
||||
this.sprite.play(`${this.npcType}_idle`);
|
||||
this.currentIdle = null;
|
||||
}, this);
|
||||
} else {
|
||||
// Fallback: simple visual effects for idle
|
||||
this.playFallbackIdle(idle);
|
||||
}
|
||||
}
|
||||
|
||||
playFallbackIdle(idleType) {
|
||||
// Create simple tween-based idle behaviors as fallback
|
||||
switch (idleType) {
|
||||
case 'fix_hat':
|
||||
this.scene.tweens.add({
|
||||
targets: this.sprite,
|
||||
angle: -5,
|
||||
duration: 200,
|
||||
yoyo: true,
|
||||
onComplete: () => {
|
||||
this.currentIdle = null;
|
||||
}
|
||||
});
|
||||
break;
|
||||
|
||||
case 'wipe_sweat':
|
||||
this.scene.tweens.add({
|
||||
targets: this.sprite,
|
||||
scaleY: 0.98,
|
||||
duration: 300,
|
||||
yoyo: true,
|
||||
onComplete: () => {
|
||||
this.currentIdle = null;
|
||||
}
|
||||
});
|
||||
break;
|
||||
|
||||
case 'look_around':
|
||||
this.scene.tweens.add({
|
||||
targets: this.sprite,
|
||||
scaleX: -Math.abs(this.sprite.scaleX),
|
||||
duration: 400,
|
||||
yoyo: true,
|
||||
onComplete: () => {
|
||||
this.currentIdle = null;
|
||||
}
|
||||
});
|
||||
break;
|
||||
|
||||
case 'scratch_head':
|
||||
this.scene.tweens.add({
|
||||
targets: this.sprite,
|
||||
y: this.sprite.y - 5,
|
||||
duration: 150,
|
||||
yoyo: true,
|
||||
repeat: 2,
|
||||
onComplete: () => {
|
||||
this.currentIdle = null;
|
||||
}
|
||||
});
|
||||
break;
|
||||
|
||||
default:
|
||||
// Generic bob
|
||||
this.scene.tweens.add({
|
||||
targets: this.sprite,
|
||||
y: this.sprite.y - 3,
|
||||
duration: 200,
|
||||
yoyo: true,
|
||||
onComplete: () => {
|
||||
this.currentIdle = null;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.sprite = null;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user