survivors: adding items and item drops

adding rarities to items
adding drops to enemies
This commit is contained in:
Sheldan
2025-09-05 22:33:09 +02:00
parent f2e62a7e74
commit 7214a64b77
7 changed files with 272 additions and 19 deletions

View File

@@ -1,10 +1,11 @@
import type {Acting, Drawable, Healthy, Placeable, Shooting} from "./interfaces.ts";
import type {Acting, ChanceEntry, Drawable, Healthy, Placeable, Shooting} from "./interfaces.ts";
import {drawDot, moveInDirectionOf} from "./utils.ts";
import {Vector} from "./base.ts";
import {World} from "./World.ts";
import type {Projectile} from "./projectile.ts";
import {ProjectileStats, StraightProjectile} from "./projectile.ts";
import {HealthPack, LevelDrop, MoneyDrop} from "./drop.ts";
import {HealthPack, ItemDrop, LevelDrop, MoneyDrop} from "./drop.ts";
import {ItemManagement} from "./items.ts";
export abstract class Enemy implements Placeable, Drawable, Acting, Healthy {
protected _position: Vector;
@@ -12,8 +13,11 @@ export abstract class Enemy implements Placeable, Drawable, Acting, Healthy {
protected world: World;
protected size: number
protected status: EnemyStatus = new EnemyStatus(10);
protected drops: KillChanceTable;
constructor(position: Vector) {
this.drops = new KillChanceTable();
this.drops.addDrop( {chance: 10, creationMethod: this.spawnMoney})
this._position = position.clone();
}
@@ -41,7 +45,14 @@ export abstract class Enemy implements Placeable, Drawable, Acting, Healthy {
}
die() {
MoneyDrop.spawnMoneyDrop(this.world, this._position);
let draw = this.drops.draw();
if(draw) {
draw.creationMethod(this)
}
}
spawnMoney(enemy: Enemy) {
MoneyDrop.spawnMoneyDrop(enemy.world, enemy._position);
}
getSize() {
@@ -225,9 +236,9 @@ export class ContainerEnemy extends Enemy {
super(position);
this.status.health = 5;
this.drops = new KillChanceTable();
this.drops.addDrop( {chance: 50, creationMethod: this.spawnHealthPack})
this.drops.addDrop( {chance: 50, creationMethod: this.spawnLevelUp})
this.drops.addDrop( {chance: 10, creationMethod: this.spawnEnemy})
ItemManagement.getItemsWithRarityFactor().forEach(drop => {
this.drops.addDrop(drop)
})
this.drops.calculateProbs()
}
@@ -245,7 +256,11 @@ export class ContainerEnemy extends Enemy {
}
die() {
this.drops.draw().creationMethod(this)
let draw = this.drops.draw();
if(draw) {
let item = draw.creationMethod(this);
ItemDrop.spawnItemDrop(this.world, item, this._position)
}
}
spawnHealthPack(enemy: ContainerEnemy) {
@@ -282,10 +297,6 @@ export class ContainerEnemy extends Enemy {
}
}
export interface ChanceEntry {
chance: number;
creationMethod: (any: any) => void;
}
export class KillChanceTable {
private chances: ChanceEntry[] = []
@@ -300,6 +311,9 @@ export class KillChanceTable {
}
draw() {
if(this.chances.length === 0) {
return undefined;
}
let change = Math.random();
for (const value of this.chances) {
change -= value.chance;

View File

@@ -1,4 +1,4 @@
import type {Acting, Drawable, Healthy, Leveling, Weapon} from "./interfaces.ts";
import type {Acting, Drawable, Healthy, Item, Leveling, Weapon} from "./interfaces.ts";
import {Vector} from "./base.ts";
import {drawDot, getCoordinatesSplit} from "./utils.ts";
@@ -8,7 +8,8 @@ export class Player implements Drawable, Acting, Healthy {
private _color: string;
private _status: PlayerStatus;
private _weapons: [Weapon] = []
private _weapons: Weapon[] = []
private _items: Item[] = []
// temp
private _speed: Vector;
@@ -47,6 +48,10 @@ export class Player implements Drawable, Acting, Healthy {
this._weapons.push(weapon)
}
addItem(item: Item) {
this._items.push(item)
}
move(direction: Vector) {
this._position = this.position.add(direction)
}
@@ -173,6 +178,31 @@ export class PlayerStats {
this._weaponRangeFactor += 0.1
}
set speed(value: number) {
this._speed = value;
}
set size(value: number) {
this._size = value;
}
set health(value: number) {
this._health = value;
}
set pullRange(value: number) {
this._pullRange = value;
}
set weaponRange(value: number) {
this._weaponRange = value;
}
set weaponRangeFactor(value: number) {
this._weaponRangeFactor = value;
}
get speed(): number {
return this._speed;
}

View File

@@ -1,7 +1,8 @@
import type {Drop} from "./interfaces.ts";
import type {Drop, Item} from "./interfaces.ts";
import {World} from "./World.ts";
import {drawDot, moveInDirectionOf} from "./utils.ts";
import {Vector} from "./base.ts";
import {rarityColor} from "./items.ts";
export abstract class BasicDrop implements Drop {
protected world: World;
@@ -120,4 +121,37 @@ export class LevelDrop extends BasicDrop {
return drop;
}
}
export class ItemDrop extends BasicDrop {
private item: Item;
constructor(world: World, position: Vector, item: Item) {
super(world, position);
this.item = item;
}
pickup() {
this.item.pickup(this.world.player, this.world)
}
draw(ctx: CanvasRenderingContext2D) {
ctx.fillStyle = rarityColor(this.item.getRarity())
ctx.fillText(this.item.name() + '', this._position.x, this._position.y)
}
static spawnItemDrop(world: World, item: Item, position?: Vector) {
world.addDrop(this.createItemDrop(world, item, position))
}
static createItemDrop(world: World, item: Item, position?: Vector) {
if(!position) {
position = world.randomPlace()
}
let drop = new ItemDrop(world, position, item)
drop.size = 3
return drop
}
}

View File

@@ -1,4 +1,7 @@
import {Vector} from "./base.ts";
import type {Player} from "./Player.ts";
import type {Rarity} from "./items.ts";
import type {World} from "./World.ts";
export interface Acting {
act()
@@ -15,6 +18,17 @@ export interface Leveling {
level()
}
export interface Item {
pickup(player: Player, world: World);
name(): string
getRarity(): Rarity;
}
export interface ChanceEntry {
chance: number;
creationMethod: (any: any) => any;
}
export interface Drop extends Drawable, Acting {
pickup()
}

View File

@@ -0,0 +1,157 @@
import type {ChanceEntry, Item} from "./interfaces.ts";
import {Player} from "./Player.ts";
import {randomItem} from "./utils.ts";
import {HomingPistol, Pistol, SpreadWeapon} from "./weapons.ts";
import type {World} from "./World.ts";
export enum Rarity {
GARBAGE= 'GARBAGE',
COMMON = 'COMMON',
UNCOMMON = 'UNCOMMON',
RARE = 'RARE',
EPIC = 'EPIC',
LEGENDARY = 'LEGENDARY',
GODLY = 'GODLY'
}
export class ItemManagement {
private static ITEMS: Item[] = []
static addItem(item: Item) {
this.ITEMS.push(item)
}
static getItemsWithRarityFactor(): ChanceEntry[] {
let items: ChanceEntry[] = []
this.ITEMS.forEach((item) => {
items.push({chance: rarityWeight(item.getRarity()), creationMethod: () => item})
})
return items;
}
static getRandomItem() {
return randomItem(this.ITEMS)
}
static initializeItems() {
this.ITEMS.push(new SpeedUp())
this.ITEMS.push(new HealthUp())
this.ITEMS.push(new HomingPistolItem())
this.ITEMS.push(new PistolItem())
this.ITEMS.push(new SpreadWeaponItem())
}
}
export function rarityWeight(rarity: Rarity): number {
switch (rarity) {
case Rarity.GARBAGE: return 80;
case Rarity.COMMON: return 65;
case Rarity.UNCOMMON: return 50;
case Rarity.RARE: return 30;
case Rarity.EPIC: return 15;
case Rarity.LEGENDARY: return 5;
case Rarity.GODLY: return 1;
}
}
export function rarityColor(rarity: Rarity): string {
switch (rarity) {
case Rarity.GARBAGE: return 'white';
case Rarity.COMMON: return 'gray';
case Rarity.UNCOMMON: return 'blue';
case Rarity.RARE: return 'green';
case Rarity.EPIC: return 'orange';
case Rarity.LEGENDARY: return 'violett';
case Rarity.GODLY: return 'red';
}
}
export abstract class BaseItem implements Item {
constructor() {
}
pickup(player: Player, world: World) {
player.addItem(this)
}
abstract name();
abstract getRarity(): Rarity;
}
export class SpeedUp extends BaseItem {
pickup(player: Player, world: World) {
player.stats.speed += 1
super.pickup(player, world)
}
name() {
return 'speed'
}
getRarity(): Rarity {
return Rarity.COMMON;
}
}
export class HealthUp extends BaseItem {
pickup(player: Player, world: World) {
player.stats.health += 1
super.pickup(player, world)
}
name() {
return 'health'
}
getRarity(): Rarity {
return Rarity.COMMON;
}
}
export class HomingPistolItem extends BaseItem {
pickup(player: Player, world: World) {
player.addWeapon(HomingPistol.generateHomingPistol(world))
super.pickup(player, world)
}
name() {
return 'homingp'
}
getRarity(): Rarity {
return Rarity.RARE;
}
}
export class PistolItem extends BaseItem {
pickup(player: Player, world: World) {
player.addWeapon(Pistol.generatePistol(world))
super.pickup(player, world)
}
name() {
return 'pistol'
}
getRarity(): Rarity {
return Rarity.RARE;
}
}
export class SpreadWeaponItem extends BaseItem {
pickup(player: Player, world: World) {
player.addWeapon(SpreadWeapon.generateSpreadWeapon(world))
super.pickup(player, world)
}
name() {
return 'spreadp'
}
getRarity(): Rarity {
return Rarity.EPIC;
}
}

View File

@@ -4,10 +4,10 @@ import {docReady} from "canvas-common";
import {World} from "./World.ts";
import {Player} from "./Player.ts";
import {Vector} from "./base.ts";
import {BasicEnemy, ContainerEnemy, Enemy, HealthEnemy, ShootingEnemy} from "./Enemies.ts";
import {BasicEnemy, ContainerEnemy, HealthEnemy, ShootingEnemy} from "./Enemies.ts";
import {HUD} from "./ui.ts";
import {HomingPistol, Pistol, SpreadWeapon} from "./weapons.ts";
import {MoneyDrop} from "./drop.ts";
import {Pistol} from "./weapons.ts";
import {ItemManagement} from "./items.ts";
let hud: HUD;
@@ -122,10 +122,10 @@ docReady(function () {
}, 10_000)
player.addWeapon(Pistol.generatePistol(world))
player.addWeapon(HomingPistol.generateHomingPistol(world))
player.addWeapon(SpreadWeapon.generateSpreadWeapon(world))
hud = new HUD(world);
ItemManagement.initializeItems()
requestAnimationFrame(updateCanvas);
})

View File

@@ -77,4 +77,8 @@ export function getCoordinatesSplit(amount: number) {
points.push(new Vector(x, y))
}
return points;
}
export function randomItem(items: any[]) {
return items[Math.floor(Math.random() * items.length)]
}