add laser weapon
This commit is contained in:
parent
8d5e7da6a6
commit
ba264d7bc5
@ -7,6 +7,7 @@ const State = @import("./state.zig");
|
|||||||
const Engine = @import("./engine/root.zig");
|
const Engine = @import("./engine/root.zig");
|
||||||
const Nanoseconds = Engine.Nanoseconds;
|
const Nanoseconds = Engine.Nanoseconds;
|
||||||
const Vec2 = Engine.Vec2;
|
const Vec2 = Engine.Vec2;
|
||||||
|
const Line = Engine.Math.Line;
|
||||||
const Rect = Engine.Math.Rect;
|
const Rect = Engine.Math.Rect;
|
||||||
const Range = Engine.Math.Range;
|
const Range = Engine.Math.Range;
|
||||||
const rgb = Engine.Math.rgb;
|
const rgb = Engine.Math.rgb;
|
||||||
@ -39,12 +40,19 @@ const Kinetic = struct {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const Gun = enum {
|
||||||
|
pistol,
|
||||||
|
bomb,
|
||||||
|
laser,
|
||||||
|
};
|
||||||
|
|
||||||
const Player = struct {
|
const Player = struct {
|
||||||
kinetic: Kinetic = .{},
|
kinetic: Kinetic = .{},
|
||||||
health: u32 = 0,
|
health: u32 = 0,
|
||||||
max_health: u32 = 0,
|
max_health: u32 = 0,
|
||||||
last_shot_at: ?Nanoseconds = null,
|
last_shot_at: ?Nanoseconds = null,
|
||||||
invincible_until: ?Nanoseconds = null,
|
invincible_until: ?Nanoseconds = null,
|
||||||
|
gun: ?Gun = null,
|
||||||
|
|
||||||
pub fn getRect(self: Player) Rect {
|
pub fn getRect(self: Player) Rect {
|
||||||
return getCenteredRect(self.kinetic.pos, 16);
|
return getCenteredRect(self.kinetic.pos, 16);
|
||||||
@ -60,6 +68,14 @@ const Bullet = struct {
|
|||||||
dead: bool = false
|
dead: bool = false
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const Laser = struct {
|
||||||
|
origin: Vec2,
|
||||||
|
dir: Vec2,
|
||||||
|
size: f32,
|
||||||
|
created_at: Nanoseconds,
|
||||||
|
duration: Nanoseconds
|
||||||
|
};
|
||||||
|
|
||||||
const Enemy = struct {
|
const Enemy = struct {
|
||||||
kinetic: Kinetic,
|
kinetic: Kinetic,
|
||||||
speed: f32,
|
speed: f32,
|
||||||
@ -105,9 +121,11 @@ gpa: Allocator,
|
|||||||
assets: *Assets,
|
assets: *Assets,
|
||||||
rng: RNGState,
|
rng: RNGState,
|
||||||
|
|
||||||
|
enemies: std.ArrayList(Enemy) = .empty,
|
||||||
|
|
||||||
player: Player = .{},
|
player: Player = .{},
|
||||||
bullets: std.ArrayList(Bullet) = .empty,
|
bullets: std.ArrayList(Bullet) = .empty,
|
||||||
enemies: std.ArrayList(Enemy) = .empty,
|
lasers: std.ArrayList(Laser) = .empty,
|
||||||
|
|
||||||
pickups: std.ArrayList(Pickup) = .empty,
|
pickups: std.ArrayList(Pickup) = .empty,
|
||||||
next_pickup_spawn_at: Nanoseconds,
|
next_pickup_spawn_at: Nanoseconds,
|
||||||
@ -131,6 +149,7 @@ pub fn init(gpa: Allocator, seed: u64, assets: *Assets, state: State) CombatScre
|
|||||||
.kinetic = .{
|
.kinetic = .{
|
||||||
.pos = world_size.divideScalar(2)
|
.pos = world_size.divideScalar(2)
|
||||||
},
|
},
|
||||||
|
.gun = .pistol,
|
||||||
.health = state.max_health,
|
.health = state.max_health,
|
||||||
.max_health = state.max_health,
|
.max_health = state.max_health,
|
||||||
},
|
},
|
||||||
@ -144,6 +163,7 @@ pub fn deinit(self: *CombatScreen) void {
|
|||||||
self.waves.deinit(self.gpa);
|
self.waves.deinit(self.gpa);
|
||||||
self.pickups.deinit(self.gpa);
|
self.pickups.deinit(self.gpa);
|
||||||
self.spawned_waves.deinit(self.gpa);
|
self.spawned_waves.deinit(self.gpa);
|
||||||
|
self.lasers.deinit(self.gpa);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn spawnEnemy(self: *CombatScreen) !void {
|
pub fn spawnEnemy(self: *CombatScreen) !void {
|
||||||
@ -291,6 +311,14 @@ pub fn tick(self: *CombatScreen, state: *State, frame: *Engine.Frame) !TickResul
|
|||||||
}
|
}
|
||||||
dir = dir.normalized();
|
dir = dir.normalized();
|
||||||
|
|
||||||
|
if (frame.isKeyPressed(._1)) {
|
||||||
|
self.player.gun = .pistol;
|
||||||
|
} else if (frame.isKeyPressed(._2)) {
|
||||||
|
self.player.gun = .bomb;
|
||||||
|
} else if (frame.isKeyPressed(._3)) {
|
||||||
|
self.player.gun = .laser;
|
||||||
|
}
|
||||||
|
|
||||||
const acceleration = 1500;
|
const acceleration = 1500;
|
||||||
|
|
||||||
self.player.kinetic.acc = dir.multiplyScalar(acceleration);
|
self.player.kinetic.acc = dir.multiplyScalar(acceleration);
|
||||||
@ -319,6 +347,10 @@ pub fn tick(self: *CombatScreen, state: *State, frame: *Engine.Frame) !TickResul
|
|||||||
|
|
||||||
if (frame.isMouseDown(.left) and cooldown_complete) {
|
if (frame.isMouseDown(.left) and cooldown_complete) {
|
||||||
self.player.last_shot_at = frame.time_ns;
|
self.player.last_shot_at = frame.time_ns;
|
||||||
|
|
||||||
|
if (self.player.gun) |gun| {
|
||||||
|
switch (gun) {
|
||||||
|
.pistol => {
|
||||||
try self.bullets.append(self.gpa, .{
|
try self.bullets.append(self.gpa, .{
|
||||||
.kinetic = .{
|
.kinetic = .{
|
||||||
.pos = self.player.kinetic.pos,
|
.pos = self.player.kinetic.pos,
|
||||||
@ -326,6 +358,20 @@ pub fn tick(self: *CombatScreen, state: *State, frame: *Engine.Frame) !TickResul
|
|||||||
.dir = bullet_dir,
|
.dir = bullet_dir,
|
||||||
.speed = 50
|
.speed = 50
|
||||||
});
|
});
|
||||||
|
},
|
||||||
|
.bomb => {
|
||||||
|
},
|
||||||
|
.laser => {
|
||||||
|
try self.lasers.append(self.gpa, .{
|
||||||
|
.origin = self.player.kinetic.pos,
|
||||||
|
.dir = bullet_dir,
|
||||||
|
.size = 10,
|
||||||
|
.created_at = frame.time_ns,
|
||||||
|
.duration = std.time.ns_per_s
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -354,6 +400,29 @@ pub fn tick(self: *CombatScreen, state: *State, frame: *Engine.Frame) !TickResul
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (self.lasers.items) |*laser| {
|
||||||
|
const laser_length = 1000;
|
||||||
|
const laser_line = Line{
|
||||||
|
.p0 = laser.origin,
|
||||||
|
.p1 = laser.origin.add(laser.dir.multiplyScalar(laser_length))
|
||||||
|
};
|
||||||
|
const laser_quad = laser_line.getQuad(laser.size);
|
||||||
|
|
||||||
|
for (self.enemies.items) |*enemy| {
|
||||||
|
const enemy_rect = getCenteredRect(enemy.kinetic.pos, enemy.size);
|
||||||
|
if (laser_quad.isRectOverlap(enemy_rect)) {
|
||||||
|
enemy.dead = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
frame.drawLine(
|
||||||
|
laser_line.p0,
|
||||||
|
laser_line.p1,
|
||||||
|
rgb(200, 20, 255),
|
||||||
|
laser.size
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
for (self.enemies.items) |*enemy| {
|
for (self.enemies.items) |*enemy| {
|
||||||
const dir_to_player = self.player.kinetic.pos.sub(enemy.kinetic.pos).normalized();
|
const dir_to_player = self.player.kinetic.pos.sub(enemy.kinetic.pos).normalized();
|
||||||
enemy.kinetic.vel = dir_to_player.multiplyScalar(50);
|
enemy.kinetic.vel = dir_to_player.multiplyScalar(50);
|
||||||
@ -444,6 +513,7 @@ pub fn tick(self: *CombatScreen, state: *State, frame: *Engine.Frame) !TickResul
|
|||||||
|
|
||||||
frame.drawTextFormat(.init(10, 30), text_opts, "{d}", .{ state.money });
|
frame.drawTextFormat(.init(10, 30), text_opts, "{d}", .{ state.money });
|
||||||
frame.drawTextFormat(.init(10, 50), text_opts, "{d}/{d}", .{ self.player.health, self.player.max_health });
|
frame.drawTextFormat(.init(10, 50), text_opts, "{d}/{d}", .{ self.player.health, self.player.max_health });
|
||||||
|
frame.drawTextFormat(.init(10, 70), text_opts, "{?}", .{ self.player.gun });
|
||||||
|
|
||||||
result.player_died = (self.player.health == 0);
|
result.player_died = (self.player.health == 0);
|
||||||
if (self.enemies.items.len == 0 and self.waves.items.len == 0 and self.spawned_waves.items.len == wave_infos.len) {
|
if (self.enemies.items.len == 0 and self.waves.items.len == 0 and self.spawned_waves.items.len == wave_infos.len) {
|
||||||
|
|||||||
@ -7,6 +7,7 @@ const simgui = sokol.imgui;
|
|||||||
const sgl = sokol.gl;
|
const sgl = sokol.gl;
|
||||||
|
|
||||||
const Math = @import("./math.zig");
|
const Math = @import("./math.zig");
|
||||||
|
const Line = Math.Line;
|
||||||
const Vec2 = Math.Vec2;
|
const Vec2 = Math.Vec2;
|
||||||
const Vec4 = Math.Vec4;
|
const Vec4 = Math.Vec4;
|
||||||
const rgb = Math.rgb;
|
const rgb = Math.rgb;
|
||||||
@ -315,14 +316,22 @@ fn drawRectangle(opts: Command.DrawRectangle) void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn drawLine(from: Vec2, to: Vec2, color: Vec4, width: f32) void {
|
fn drawLine(from: Vec2, to: Vec2, color: Vec4, width: f32) void {
|
||||||
const step = to.sub(from).normalized().multiplyScalar(width / 2);
|
const line = Line{
|
||||||
|
.p0 = from,
|
||||||
|
.p1 = to,
|
||||||
|
};
|
||||||
|
|
||||||
const top_left = from.add(step.rotateLeft90());
|
const quad = line.getQuad(width);
|
||||||
const bottom_left = from.add(step.rotateRight90());
|
|
||||||
const top_right = to.add(step.rotateLeft90());
|
|
||||||
const bottom_right = to.add(step.rotateRight90());
|
|
||||||
|
|
||||||
drawQuadNoUVs(.{ top_right, top_left, bottom_left, bottom_right }, color);
|
drawQuadNoUVs(
|
||||||
|
.{
|
||||||
|
quad.top_right,
|
||||||
|
quad.top_left,
|
||||||
|
quad.bottom_left,
|
||||||
|
quad.bottom_right
|
||||||
|
},
|
||||||
|
color
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn addFont(name: [*c]const u8, data: []const u8) !Font.Id {
|
pub fn addFont(name: [*c]const u8, data: []const u8) !Font.Id {
|
||||||
|
|||||||
@ -397,6 +397,33 @@ pub const Rect = struct {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn getTopEdge(self: Rect) Line {
|
||||||
|
return Line{
|
||||||
|
.p0 = .init(self.left(), self.top()),
|
||||||
|
.p1 = .init(self.right(), self.top()),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
pub fn getBottomEdge(self: Rect) Line {
|
||||||
|
return Line{
|
||||||
|
.p0 = .init(self.left(), self.bottom()),
|
||||||
|
.p1 = .init(self.right(), self.bottom()),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getLeftEdge(self: Rect) Line {
|
||||||
|
return Line{
|
||||||
|
.p0 = .init(self.left(), self.top()),
|
||||||
|
.p1 = .init(self.left(), self.bottom()),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getRightEdge(self: Rect) Line {
|
||||||
|
return Line{
|
||||||
|
.p0 = .init(self.right(), self.top()),
|
||||||
|
.p1 = .init(self.right(), self.bottom()),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
pub fn left(self: Rect) f32 {
|
pub fn left(self: Rect) f32 {
|
||||||
return self.pos.x;
|
return self.pos.x;
|
||||||
}
|
}
|
||||||
@ -431,12 +458,20 @@ pub const Rect = struct {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn isInside(self: Rect, pos: Vec2) bool {
|
pub fn isPointInside(self: Rect, pos: Vec2) bool {
|
||||||
const x_overlap = self.pos.x <= pos.x and pos.x < self.pos.x + self.size.x;
|
const x_overlap = self.pos.x <= pos.x and pos.x < self.pos.x + self.size.x;
|
||||||
const y_overlap = self.pos.y <= pos.y and pos.y < self.pos.y + self.size.y;
|
const y_overlap = self.pos.y <= pos.y and pos.y < self.pos.y + self.size.y;
|
||||||
return x_overlap and y_overlap;
|
return x_overlap and y_overlap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn checkEdgeLineOverlap(self: Rect, line: Line) bool {
|
||||||
|
const left_overlap = line.hasOverlap(self.getLeftEdge());
|
||||||
|
const right_overlap = line.hasOverlap(self.getRightEdge());
|
||||||
|
const top_overlap = line.hasOverlap(self.getTopEdge());
|
||||||
|
const bottom_overlap = line.hasOverlap(self.getBottomEdge());
|
||||||
|
return left_overlap or right_overlap or top_overlap or bottom_overlap;
|
||||||
|
}
|
||||||
|
|
||||||
pub fn hasOverlap(lhs: Rect, rhs: Rect) bool {
|
pub fn hasOverlap(lhs: Rect, rhs: Rect) bool {
|
||||||
return (lhs.left() < rhs.right() and lhs.right() > rhs.left()) and
|
return (lhs.left() < rhs.right() and lhs.right() > rhs.left()) and
|
||||||
(lhs.top() < rhs.bottom() and lhs.bottom() > rhs.top());
|
(lhs.top() < rhs.bottom() and lhs.bottom() > rhs.top());
|
||||||
@ -461,9 +496,133 @@ pub const Rect = struct {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub const Quad = struct {
|
||||||
|
top_left: Vec2,
|
||||||
|
bottom_left: Vec2,
|
||||||
|
top_right: Vec2,
|
||||||
|
bottom_right: Vec2,
|
||||||
|
|
||||||
|
pub fn isRectOverlap(self: Quad, rect: Rect) bool {
|
||||||
|
const vertices = [4]Vec2{
|
||||||
|
self.top_right,
|
||||||
|
self.top_left,
|
||||||
|
self.bottom_left,
|
||||||
|
self.bottom_right,
|
||||||
|
};
|
||||||
|
const polygon = Polygon{
|
||||||
|
.vertices = &vertices
|
||||||
|
};
|
||||||
|
return polygon.isRectOverlap(rect);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Polygon = struct {
|
||||||
|
vertices: []const Vec2,
|
||||||
|
|
||||||
|
pub fn isPointInside(self: Polygon, point: Vec2) bool {
|
||||||
|
var collision = false;
|
||||||
|
|
||||||
|
const py = point.y;
|
||||||
|
const px = point.x;
|
||||||
|
|
||||||
|
for (0..self.vertices.len) |i| {
|
||||||
|
const next_i = @mod(i + 1, self.vertices.len);
|
||||||
|
// get the PVectors at our current position
|
||||||
|
// this makes our if statement a little cleaner
|
||||||
|
const vc = self.vertices[i]; // c for "current"
|
||||||
|
const vn = self.vertices[next_i]; // n for "next"
|
||||||
|
|
||||||
|
// compare position, flip 'collision' variable
|
||||||
|
// back and forth
|
||||||
|
if (
|
||||||
|
((vc.y > py and vn.y < py) or (vc.y < py and vn.y > py))
|
||||||
|
and
|
||||||
|
(px < (vn.x-vc.x)*(py-vc.y) / (vn.y-vc.y)+vc.x)
|
||||||
|
) {
|
||||||
|
collision = !collision;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return collision;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn isRectOverlap(self: Polygon, rect: Rect) bool {
|
||||||
|
// go through each of the vertices, plus the next
|
||||||
|
// vertex in the list
|
||||||
|
for (0..self.vertices.len) |i| {
|
||||||
|
const next_i = @mod(i + 1, self.vertices.len);
|
||||||
|
|
||||||
|
// get the PVectors at our current position
|
||||||
|
// this makes our if statement a little cleaner
|
||||||
|
const vc = self.vertices[i]; // c for "current"
|
||||||
|
const vn = self.vertices[next_i]; // n for "next"
|
||||||
|
|
||||||
|
const polygon_edge = Line{
|
||||||
|
.p0 = vc,
|
||||||
|
.p1 = vn,
|
||||||
|
};
|
||||||
|
|
||||||
|
// check against all four sides of the rectangle
|
||||||
|
if (rect.checkEdgeLineOverlap(polygon_edge)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// optional: test if the rectangle is INSIDE the polygon
|
||||||
|
// note that this iterates all sides of the polygon
|
||||||
|
// again, so only use this if you need to
|
||||||
|
if (self.isPointInside(rect.pos)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
pub const Line = struct {
|
pub const Line = struct {
|
||||||
p0: Vec2,
|
p0: Vec2,
|
||||||
p1: Vec2
|
p1: Vec2,
|
||||||
|
|
||||||
|
pub fn hasOverlap(lhs: Line, rhs: Line) bool {
|
||||||
|
const x1 = lhs.p0.x;
|
||||||
|
const y1 = lhs.p0.y;
|
||||||
|
const x2 = lhs.p1.x;
|
||||||
|
const y2 = lhs.p1.y;
|
||||||
|
const x3 = rhs.p0.x;
|
||||||
|
const y3 = rhs.p0.y;
|
||||||
|
const x4 = rhs.p1.x;
|
||||||
|
const y4 = rhs.p1.y;
|
||||||
|
|
||||||
|
// calculate the direction of the lines
|
||||||
|
const uA = ((x4-x3)*(y1-y3) - (y4-y3)*(x1-x3)) / ((y4-y3)*(x2-x1) - (x4-x3)*(y2-y1));
|
||||||
|
const uB = ((x2-x1)*(y1-y3) - (y2-y1)*(x1-x3)) / ((y4-y3)*(x2-x1) - (x4-x3)*(y2-y1));
|
||||||
|
|
||||||
|
// if uA and uB are between 0-1, lines are colliding
|
||||||
|
if (uA >= 0 and uA <= 1 and uB >= 0 and uB <= 1) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getQuad(self: Line, width: f32) Quad {
|
||||||
|
const to = self.p1;
|
||||||
|
const from = self.p0;
|
||||||
|
|
||||||
|
const step = to.sub(from).normalized().multiplyScalar(width / 2);
|
||||||
|
|
||||||
|
const top_left = from.add(step.rotateLeft90());
|
||||||
|
const bottom_left = from.add(step.rotateRight90());
|
||||||
|
const top_right = to.add(step.rotateLeft90());
|
||||||
|
const bottom_right = to.add(step.rotateRight90());
|
||||||
|
|
||||||
|
return Quad{
|
||||||
|
.top_left = top_left,
|
||||||
|
.bottom_left = bottom_left,
|
||||||
|
.top_right = top_right,
|
||||||
|
.bottom_right = bottom_right
|
||||||
|
};
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn isInsideRect(rect_pos: Vec2, rect_size: Vec2, pos: Vec2) bool {
|
pub fn isInsideRect(rect_pos: Vec2, rect_size: Vec2, pos: Vec2) bool {
|
||||||
@ -471,7 +630,7 @@ pub fn isInsideRect(rect_pos: Vec2, rect_size: Vec2, pos: Vec2) bool {
|
|||||||
.pos = rect_pos,
|
.pos = rect_pos,
|
||||||
.size = rect_size
|
.size = rect_size
|
||||||
};
|
};
|
||||||
return rect.isInside(pos);
|
return rect.isPointInside(pos);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn rgba(r: u8, g: u8, b: u8, a: f32) Vec4 {
|
pub fn rgba(r: u8, g: u8, b: u8, a: f32) Vec4 {
|
||||||
|
|||||||
@ -134,7 +134,7 @@ pub fn tick(self: *ShopScreen, state: *State, frame: *Frame) !TickResult {
|
|||||||
for (self.nodes.items) |*node| {
|
for (self.nodes.items) |*node| {
|
||||||
const node_rect = Rect.initCentered(node.pos.x, node.pos.y, node_size.x, node_size.y);
|
const node_rect = Rect.initCentered(node.pos.x, node.pos.y, node_size.x, node_size.y);
|
||||||
|
|
||||||
const is_mouse_inside = mouse != null and node_rect.isInside(mouse.?);
|
const is_mouse_inside = mouse != null and node_rect.isPointInside(mouse.?);
|
||||||
const has_enough_money = state.money >= node.money_cost;
|
const has_enough_money = state.money >= node.money_cost;
|
||||||
|
|
||||||
if (has_enough_money and is_mouse_inside and frame.isMousePressed(.left)) {
|
if (has_enough_money and is_mouse_inside and frame.isMousePressed(.left)) {
|
||||||
@ -173,7 +173,7 @@ pub fn tick(self: *ShopScreen, state: *State, frame: *Frame) !TickResult {
|
|||||||
.color = rgb(255, 255, 255)
|
.color = rgb(255, 255, 255)
|
||||||
});
|
});
|
||||||
|
|
||||||
if (frame.input.mouse_position != null and back_rect.isInside(frame.input.mouse_position.?) and frame.isMousePressed(.left)) {
|
if (frame.input.mouse_position != null and back_rect.isPointInside(frame.input.mouse_position.?) and frame.isMousePressed(.left)) {
|
||||||
result.back_to_combat = true;
|
result.back_to_combat = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user