diff --git a/public/assets/ammo/energy/microfusion_cell/icon.webp b/public/assets/ammo/energy/microfusion_cell/icon.webp new file mode 100644 index 0000000..73ce187 Binary files /dev/null and b/public/assets/ammo/energy/microfusion_cell/icon.webp differ diff --git a/public/assets/units/vault13_male/dead/vault13_male__dead__rifle.webp b/public/assets/units/vault13_male/dead/vault13_male__dead__rifle.webp new file mode 100644 index 0000000..589907e Binary files /dev/null and b/public/assets/units/vault13_male/dead/vault13_male__dead__rifle.webp differ diff --git a/public/assets/weapons/firearm/rifle/laser_rifle/icon.webp b/public/assets/weapons/firearm/rifle/laser_rifle/icon.webp new file mode 100644 index 0000000..1bf374a Binary files /dev/null and b/public/assets/weapons/firearm/rifle/laser_rifle/icon.webp differ diff --git a/public/assets/weapons/firearm/rifle/laser_rifle/out_of_ammo.m4a b/public/assets/weapons/firearm/rifle/laser_rifle/out_of_ammo.m4a new file mode 100644 index 0000000..ed8ca34 Binary files /dev/null and b/public/assets/weapons/firearm/rifle/laser_rifle/out_of_ammo.m4a differ diff --git a/public/assets/weapons/firearm/rifle/laser_rifle/reload.m4a b/public/assets/weapons/firearm/rifle/laser_rifle/reload.m4a new file mode 100644 index 0000000..a104b34 Binary files /dev/null and b/public/assets/weapons/firearm/rifle/laser_rifle/reload.m4a differ diff --git a/public/assets/weapons/firearm/rifle/laser_rifle/shot_single_1.m4a b/public/assets/weapons/firearm/rifle/laser_rifle/shot_single_1.m4a new file mode 100644 index 0000000..7d26131 Binary files /dev/null and b/public/assets/weapons/firearm/rifle/laser_rifle/shot_single_1.m4a differ diff --git a/public/assets/weapons/firearm/rifle/laser_rifle/shot_single_2.m4a b/public/assets/weapons/firearm/rifle/laser_rifle/shot_single_2.m4a new file mode 100644 index 0000000..f4f710a Binary files /dev/null and b/public/assets/weapons/firearm/rifle/laser_rifle/shot_single_2.m4a differ diff --git a/public/maps/map_shooting_gallery.json b/public/maps/map_shooting_gallery.json index f52e03a..b8d1ad0 100644 --- a/public/maps/map_shooting_gallery.json +++ b/public/maps/map_shooting_gallery.json @@ -15,6 +15,7 @@ "rotation": 0, "inventory": { "main": [ + { "class": "laser_ammo", "name": "microfusion_cell", "quantity": 999 }, { "class": "firearm_ammo", "name": ".45", "quantity": 2 }, { "class": "firearm_ammo", "name": "9mm_ball", "quantity": 999 }, { "class": "firearm_ammo", "name": "9mm_AP", "quantity": 999 }, @@ -28,7 +29,7 @@ { "class": "weapon", "name": "throwing_knife" }, { "class": "weapon", "name": "sniper_rifle" } ], - "leftHand": { "class": "weapon", "name": "frag_grenade" }, + "leftHand": { "class": "weapon", "name": "laser_rifle" }, "rightHand": { "class": "weapon", "name": "mp5" } } }, diff --git a/public/media-assets-manifest.json b/public/media-assets-manifest.json index 9178ab6..645730e 100644 --- a/public/media-assets-manifest.json +++ b/public/media-assets-manifest.json @@ -1,8 +1,8 @@ { "image": { "total": { - "count": 144, - "size": 6676361 + "count": 146, + "size": 6678635 }, "files": { "/assets/UI/cursors/default.png": { @@ -119,6 +119,12 @@ "size": 566, "type": "image" }, + "/assets/ammo/energy/microfusion_cell/icon.webp": { + "path": "/react-isometric-game/assets/ammo/energy/microfusion_cell/icon.webp", + "name": "icon.webp", + "size": 784, + "type": "image" + }, "/assets/bg.webp": { "path": "/react-isometric-game/assets/bg.webp", "name": "bg.webp", @@ -815,6 +821,12 @@ "size": 718, "type": "image" }, + "/assets/weapons/firearm/rifle/laser_rifle/icon.webp": { + "path": "/react-isometric-game/assets/weapons/firearm/rifle/laser_rifle/icon.webp", + "name": "icon.webp", + "size": 1490, + "type": "image" + }, "/assets/weapons/firearm/rifle/sniper_rifle/icon.webp": { "path": "/react-isometric-game/assets/weapons/firearm/rifle/sniper_rifle/icon.webp", "name": "icon.webp", @@ -873,8 +885,8 @@ }, "audio": { "total": { - "count": 65, - "size": 754594 + "count": 69, + "size": 811103 }, "files": { "/assets/UI/button.m4a": { @@ -1129,6 +1141,30 @@ "size": 6061, "type": "audio" }, + "/assets/weapons/firearm/rifle/laser_rifle/out_of_ammo.m4a": { + "path": "/react-isometric-game/assets/weapons/firearm/rifle/laser_rifle/out_of_ammo.m4a", + "name": "out_of_ammo.m4a", + "size": 16142, + "type": "audio" + }, + "/assets/weapons/firearm/rifle/laser_rifle/reload.m4a": { + "path": "/react-isometric-game/assets/weapons/firearm/rifle/laser_rifle/reload.m4a", + "name": "reload.m4a", + "size": 15624, + "type": "audio" + }, + "/assets/weapons/firearm/rifle/laser_rifle/shot_single_1.m4a": { + "path": "/react-isometric-game/assets/weapons/firearm/rifle/laser_rifle/shot_single_1.m4a", + "name": "shot_single_1.m4a", + "size": 12251, + "type": "audio" + }, + "/assets/weapons/firearm/rifle/laser_rifle/shot_single_2.m4a": { + "path": "/react-isometric-game/assets/weapons/firearm/rifle/laser_rifle/shot_single_2.m4a", + "name": "shot_single_2.m4a", + "size": 12492, + "type": "audio" + }, "/assets/weapons/firearm/rifle/sniper_rifle/out_of_ammo.m4a": { "path": "/react-isometric-game/assets/weapons/firearm/rifle/sniper_rifle/out_of_ammo.m4a", "name": "out_of_ammo.m4a", diff --git a/src/components/_modals/inventory/_shared/InventoryItemInfoPanel.tsx b/src/components/_modals/inventory/_shared/InventoryItemInfoPanel.tsx index 1348e71..1aedb56 100644 --- a/src/components/_modals/inventory/_shared/InventoryItemInfoPanel.tsx +++ b/src/components/_modals/inventory/_shared/InventoryItemInfoPanel.tsx @@ -1,7 +1,7 @@ -import { NothingSelectedText } from "@src/components/editor/_shared/NothingSelectedText"; import { AmmoDetailsList } from "@src/components/_modals/inventory/_shared/AmmoDetailsList"; import { ItemImage } from "@src/components/_modals/inventory/_shared/ItemImage"; import { WeaponDetailsList } from "@src/components/_modals/inventory/_shared/WeaponDetailsList"; +import { NothingSelectedText } from "@src/components/editor/_shared/NothingSelectedText"; import { AmmoDictEntity } from "@src/dict/ammo/ammo"; import { WeaponDictEntity } from "@src/dict/weapon/weapon"; import React from "react"; @@ -16,6 +16,7 @@ const ItemInfoComponent = (dictEntity: InventoryItemInfoProps["dictEntity"]) => return ; case "firearm_ammo": + case "laser_ammo": case "melee_ammo": case "grenade_ammo": return ; diff --git a/src/components/viewport/layers/ammo/Ammo.tsx b/src/components/viewport/layers/ammo/Ammo.tsx index d65cee9..629cb9d 100644 --- a/src/components/viewport/layers/ammo/Ammo.tsx +++ b/src/components/viewport/layers/ammo/Ammo.tsx @@ -1,15 +1,20 @@ -import { SingleUnitAmmo } from "@src/components/viewport/layers/ammo/SingleUnitAmmo"; import { GameLayer } from "@src/components/viewport/_shared/GameLayer"; +import { SingleUnitAmmo } from "@src/components/viewport/layers/ammo/SingleUnitAmmo"; import { useGameState } from "@src/hooks/useGameState"; +import React from "react"; -export const Ammo = () => { +export const Ammo = React.memo(() => { const { gameState } = useGameState(); + const ammoFiredIds = React.useMemo(() => { + return gameState.ammoFiredIds; + }, [gameState.ammoFiredIds.length]); + return ( - {Object.values(gameState.ammoFiredIds).map((ammoId) => ( - + {Object.values(ammoFiredIds).map((ammoId) => ( + ))} ); -}; +}); diff --git a/src/components/viewport/layers/ammo/SingleUnitAmmo.tsx b/src/components/viewport/layers/ammo/SingleUnitAmmo.tsx index e9b9172..dc33649 100644 --- a/src/components/viewport/layers/ammo/SingleUnitAmmo.tsx +++ b/src/components/viewport/layers/ammo/SingleUnitAmmo.tsx @@ -1,19 +1,17 @@ -import { constants } from "@src/engine/constants"; import { Ammo } from "@src/engine/weapon/AmmoFactory"; +import { useGameState } from "@src/hooks/useGameState"; +import React from "react"; -export const SingleUnitAmmo = (props: { ammo?: Ammo }) => { - if (!props.ammo) return null; +export const SingleUnitAmmo = (props: { ammoId?: Ammo["id"] }) => { + const { gameState } = useGameState(); - const position = props.ammo.position; + const ammo = React.useMemo(() => { + if (!props.ammoId) return null; - return ( -
- ); + return gameState.getAmmoById(props.ammoId); + }, [props.ammoId]); + + if (!ammo) return null; + + return
; }; diff --git a/src/dict/ammo/ammo.ts b/src/dict/ammo/ammo.ts index c2260fe..a5a7a6c 100644 --- a/src/dict/ammo/ammo.ts +++ b/src/dict/ammo/ammo.ts @@ -1,6 +1,7 @@ import ammo_45 from "@src/dict/ammo/.45/.45"; import ammo_7_62mm from "@src/dict/ammo/7.62mm/7.62mm"; import ammo_9mm from "@src/dict/ammo/9mm/_9mm"; +import microfusion_cell from "@src/dict/ammo/energy/microfusion_cell"; import grenade from "@src/dict/ammo/grenade/_grenade"; import melee from "@src/dict/ammo/melee/_melee"; import { VfxLight, VfxType } from "@src/engine/vfx/VfxFactory"; @@ -9,6 +10,7 @@ export enum weaponAmmoClassNames { "firearm_ammo" = "Firearm ammo", "grenade_ammo" = "Grenade ammo", "melee_ammo" = "Melee ammo", + "laser_ammo" = "Laser ammo", } export type WeaponAmmoClass = keyof typeof weaponAmmoClassNames; @@ -22,6 +24,8 @@ export type WeaponAmmoType = | "7.62mm" | "9mm" // + | "microfusion_cell" + // | "molotov_cocktail" | "frag_grenade" // @@ -76,7 +80,7 @@ export type AmmoDictEntity = { vfx?: WeaponAmmoVfx; }; -const ammoList = { ...ammo_45, ...ammo_9mm, ...ammo_7_62mm, ...grenade, ...melee }; +const ammoList = { ...ammo_45, ...ammo_9mm, ...ammo_7_62mm, ...microfusion_cell, ...grenade, ...melee }; export type AmmoName = Exclude; export default function getAmmoDictList(skipFakeAmmo = false) { diff --git a/src/dict/ammo/energy/microfusion_cell.ts b/src/dict/ammo/energy/microfusion_cell.ts new file mode 100644 index 0000000..69999f2 --- /dev/null +++ b/src/dict/ammo/energy/microfusion_cell.ts @@ -0,0 +1,49 @@ +import { AmmoDictEntity } from "@src/dict/ammo/ammo"; + +const ammo_microfusion_cell: { [id: string]: AmmoDictEntity } = { + microfusion_cell: { + name: "microfusion_cell", + class: "laser_ammo", + type: "microfusion_cell", + title: "Microfusion cell", + description: "A medium sized energy production unit. Self-contained fusion plant.", + speed: 50, + damage: 0, + penetration: 0, + magazineSize: 50, + weight: 5, + price: 4, + gfx: { + icon: { + size: { width: 36, height: 51 }, + src: "/assets/ammo/energy/microfusion_cell/icon.webp", + }, + isometric: { + size: { width: 29, height: 26 }, + src: "/assets/ammo/energy/microfusion_cell/iso.webp", + }, + }, + sfx: { + targetReached: { + src: [ + "/assets/weapons/firearm/rico_1.mp3", + "/assets/weapons/firearm/rico_2.mp3", + "/assets/weapons/firearm/rico_3.mp3", + "/assets/weapons/firearm/rico_4.mp3", + "/assets/weapons/firearm/rico_5.mp3", + "/assets/weapons/firearm/rico_6.mp3", + "/assets/weapons/firearm/rico_7.mp3", + ], + }, + }, + vfx: { + targetReached: { + type: ["hit-ground"], + delayBeforeEmitting: 50, + animationDuration: 200, + }, + }, + }, +}; + +export default ammo_microfusion_cell; diff --git a/src/dict/weapon/firearm/firearm.ts b/src/dict/weapon/firearm/firearm.ts index 5f4039d..846fbcc 100644 --- a/src/dict/weapon/firearm/firearm.ts +++ b/src/dict/weapon/firearm/firearm.ts @@ -1,5 +1,6 @@ import beretta_M92FS from "@src/dict/weapon/firearm/pistol/beretta_M92FS"; import colt_45 from "@src/dict/weapon/firearm/pistol/colt_45"; +import laser_rifle from "@src/dict/weapon/firearm/rifle/laser_rifle"; import sniper_rifle from "@src/dict/weapon/firearm/rifle/sniper_rifle"; import mp5 from "@src/dict/weapon/firearm/smg/MP5_H&K"; import { WeaponDictEntity } from "@src/dict/weapon/weapon"; @@ -11,6 +12,7 @@ export const firearm: { [id: string]: WeaponDictEntity } = { mp5, // sniper_rifle, + laser_rifle, }; export default firearm; diff --git a/src/dict/weapon/firearm/rifle/laser_rifle.ts b/src/dict/weapon/firearm/rifle/laser_rifle.ts new file mode 100644 index 0000000..8f91c07 --- /dev/null +++ b/src/dict/weapon/firearm/rifle/laser_rifle.ts @@ -0,0 +1,94 @@ +import { WeaponDictEntity } from "@src/dict/weapon/weapon"; + +const laser_rifle: WeaponDictEntity = { + name: "laser_rifle", + class: "firearm", + type: "rifle", + title: "Laser rifle", + description: + "A Wattz 2000 laser rifle. Uses micro fusion cells for more powerful lasers, and an extended barrel for additional range.", + weight: 12, + price: 6000, + damageType: "energy", + ammoCapacity: 12, + requiredStat: { + name: "strength", + value: 5, + }, + attackModes: { + shot_single: { + skill: "smallGuns", + ammoType: "microfusion_cell", + actionPointsConsumption: 5, + ammoConsumption: 1, + range: 45, + damage: { + min: 23, + max: 50, + }, + removeFromInventoryAfterUse: false, + animationDuration: { + attack: 400, + attackCompleted: 1000, + attackNotAllowed: 1000, + }, + }, + shot_burst: { + skill: "smallGuns", + ammoType: "microfusion_cell", + actionPointsConsumption: 5, + ammoConsumption: 6, + range: 20, + damage: { + min: 8, + max: 17, + }, + removeFromInventoryAfterUse: false, + animationDuration: { + attack: 400, + attackCompleted: 1000, + attackNotAllowed: 1000, + }, + }, + }, + sfx: { + shot_single: { + src: [ + "/assets/weapons/firearm/rifle/laser_rifle/shot_single_1.m4a", + "/assets/weapons/firearm/rifle/laser_rifle/shot_single_2.m4a", + ], + timeIntervalMs: 400, + }, + outOfAmmo: { + src: ["/assets/weapons/firearm/rifle/laser_rifle/out_of_ammo.m4a"], + }, + reload: { + src: ["/assets/weapons/firearm/rifle/laser_rifle/reload.m4a"], + }, + }, + gfx: { + icon: { + size: { width: 128, height: 36 }, + src: "/assets/weapons/firearm/rifle/laser_rifle/icon.webp", + }, + isometric: { + size: { width: 64, height: 21 }, + src: "/assets/weapons/firearm/rifle/laser_rifle/iso.webp", + }, + }, + vfx: { + shot_single: { + type: ["muzzle-1", "muzzle-2", "muzzle-3"], + animationDuration: 100, + delayBetweenEmitting: 50, + delayBeforeEmitting: 500, + light: { + animationDuration: 50, + color: "#ff0000", + radius: 2, + }, + }, + }, +}; + +export default laser_rifle; diff --git a/src/dict/weapon/weapon.ts b/src/dict/weapon/weapon.ts index e88a583..ccafc98 100644 --- a/src/dict/weapon/weapon.ts +++ b/src/dict/weapon/weapon.ts @@ -27,7 +27,7 @@ export type WeaponDamage = { min: number; max: number; }; -export type WeaponDamageType = "normal" | "explosion" | "fire"; +export type WeaponDamageType = "normal" | "explosion" | "fire" | "energy"; export type WeaponAttackMode = keyof typeof weaponAttackModes; export type WeaponSfxType = WeaponAttackMode | "outOfAmmo" | "reload"; diff --git a/src/engine/weapon/AmmoFactory.ts b/src/engine/weapon/AmmoFactory.ts index 201a3c0..4ad8f04 100644 --- a/src/engine/weapon/AmmoFactory.ts +++ b/src/engine/weapon/AmmoFactory.ts @@ -1,5 +1,7 @@ import { StaticMapWeaponAmmo } from "@src/context/GameStateContext"; import { AmmoDictEntity, AmmoName, WeaponAmmoClass } from "@src/dict/ammo/ammo"; +import { InventoryItem } from "@src/engine/InventoryItemFactory"; +import { constants } from "@src/engine/constants"; import { GameMap } from "@src/engine/gameMap"; import { getAngleBetweenTwoGridPoints, @@ -7,8 +9,8 @@ import { gridToScreenSpace, randomUUID, } from "@src/engine/helpers"; -import { InventoryItem } from "@src/engine/InventoryItemFactory"; import { Weapon } from "@src/engine/weapon/WeaponFactory"; +import { CSSProperties } from "react"; export class Ammo extends InventoryItem { readonly class: WeaponAmmoClass; @@ -110,6 +112,14 @@ export class Ammo extends InventoryItem { delete gameState.ammo[this.id]; } + getDerivedCssProps(): CSSProperties { + return { + left: this.position.screen.x + constants.tileSize.width / 2, + top: this.position.screen.y + constants.tileSize.height / 2, + transform: `rotateX(60deg) rotateZ(${this.angle.deg - 45}deg) translateZ(40px)`, + }; + } + getJSON(): StaticMapWeaponAmmo { return { class: this.class, diff --git a/src/engine/weapon/firearm/FirearmFactory.ts b/src/engine/weapon/firearm/FirearmFactory.ts index 5ac1082..00f57d6 100644 --- a/src/engine/weapon/firearm/FirearmFactory.ts +++ b/src/engine/weapon/firearm/FirearmFactory.ts @@ -4,8 +4,9 @@ import { randomInt } from "@src/engine/helpers"; import { Unit } from "@src/engine/unit/UnitFactory"; import { Vfx } from "@src/engine/vfx/VfxFactory"; import { Ammo } from "@src/engine/weapon/AmmoFactory"; -import { FirearmAmmo } from "@src/engine/weapon/firearm/FirearmAmmoFactory"; import { Weapon } from "@src/engine/weapon/WeaponFactory"; +import { FirearmAmmo } from "@src/engine/weapon/firearm/FirearmAmmoFactory"; +import { LaserAmmo } from "@src/engine/weapon/laser/LaserAmmoFactory"; export class Firearm extends Weapon { ammoCurrent: Ammo[] = []; @@ -97,7 +98,11 @@ export class Firearm extends Weapon { const currentAttackModeDetails = this.getCurrentAttackModeDetails(); const ammoItems = this.owner.inventory.main - .filter((item) => item instanceof FirearmAmmo && item.dictEntity.type === currentAttackModeDetails.ammoType) + .filter( + (item) => + (item instanceof FirearmAmmo || item instanceof LaserAmmo) && + item.dictEntity.type === currentAttackModeDetails.ammoType, + ) .slice(0, this.dictEntity.ammoCapacity! - this.ammoCurrent.length) as Ammo[]; if (ammoItems.length === 0) { diff --git a/src/engine/weapon/helpers.ts b/src/engine/weapon/helpers.ts index 8be9111..f1685ad 100644 --- a/src/engine/weapon/helpers.ts +++ b/src/engine/weapon/helpers.ts @@ -5,6 +5,7 @@ import { Unit } from "@src/engine/unit/UnitFactory"; import { Ammo, AmmoFactory } from "@src/engine/weapon/AmmoFactory"; import { FirearmAmmo } from "@src/engine/weapon/firearm/FirearmAmmoFactory"; import { Firearm } from "@src/engine/weapon/firearm/FirearmFactory"; +import { LaserAmmo } from "@src/engine/weapon/laser/LaserAmmoFactory"; import { MeleePunch } from "@src/engine/weapon/melee/meleePunchFactory"; import { MeleeWeapon } from "@src/engine/weapon/melee/MeleeWeaponFactory"; import { ProjectileAmmo } from "@src/engine/weapon/throwable/ProjectileAmmoFactory"; @@ -65,6 +66,7 @@ export function createAmmoByName(ammoName: AmmoName) { firearm_ammo: FirearmAmmo, grenade_ammo: ProjectileAmmo, melee_ammo: MeleePunch, + laser_ammo: LaserAmmo, }; const ammoDictEntity = getAmmoDictEntityByName(ammoName); diff --git a/src/engine/weapon/laser/LaserAmmoFactory.ts b/src/engine/weapon/laser/LaserAmmoFactory.ts new file mode 100644 index 0000000..90b724e --- /dev/null +++ b/src/engine/weapon/laser/LaserAmmoFactory.ts @@ -0,0 +1,23 @@ +import { AmmoDictEntity, AmmoName } from "@src/dict/ammo/ammo"; +import { Ammo } from "@src/engine//weapon/AmmoFactory"; +import { constants } from "@src/engine/constants"; +import { getDistanceBetweenGridPoints } from "@src/engine/helpers"; + +export class LaserAmmo extends Ammo { + constructor(ammoName: AmmoName, ammoDictEntity: AmmoDictEntity) { + super(ammoName, ammoDictEntity); + } + + getDerivedCssProps() { + return { + ...super.getDerivedCssProps(), + + left: this.startPosition.screen.x + constants.tileSize.width / 2, + top: this.startPosition.screen.y + constants.tileSize.height / 2, + height: + getDistanceBetweenGridPoints(this.startPosition.grid, this.targetPosition.grid) * + constants.wireframeTileSize.width, + transform: `rotateX(60deg) rotateZ(${this.angle.deg - 45}deg) translateZ(20px)`, + }; + } +} diff --git a/src/styles/weapons/ammo.less b/src/styles/weapons/ammo.less index e06458f..2d215d5 100644 --- a/src/styles/weapons/ammo.less +++ b/src/styles/weapons/ammo.less @@ -29,6 +29,14 @@ transform-origin: left top; border-radius: 0 0 50% 50%; - width: 5px !important; - height: 10px !important; + width: 5px; + height: 10px; + + &[data-name="microfusion_cell"] { + border-radius: 0; + width: 1px; + height: 50px; + background-color: rgba(255, 0, 0, 0.5); + box-shadow: 0 0 2px 2px rgba(255, 0, 0, 0.5); + } }