From a9efa0f01cae990dd8616f4bd4e0e19723fe06b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Madar=C3=A1sz?= Date: Wed, 26 Jul 2023 19:05:50 +0200 Subject: [PATCH] big chungus --- code/foundation/src/core/game.h | 1 + code/foundation/src/models/components.h | 5 ++ .../src/models/prefabs/prefabs_list.c | 3 - .../src/systems/modules/system_health.c | 1 + .../src/systems/modules/system_vehicle.c | 8 +++ code/foundation/src/world/world.c | 5 ++ code/foundation/src/world/world.h | 2 + code/games/minimal/src/game.c | 4 ++ code/games/sandbox/src/game.c | 4 ++ code/games/survival/src/game.c | 64 ++++++++++++++++++- code/games/survival/src/main.c | 8 +-- code/games/survival/src/platform.c | 2 + code/games/survival/src/renderer.c | 17 +++-- code/games/survival/src/system_mob.c | 24 +++++-- code/games/survival/src/worldgen.c | 56 ++++++++++++++++ win/survival_package_web.bat | 14 ++++ 16 files changed, 197 insertions(+), 21 deletions(-) create mode 100644 win/survival_package_web.bat diff --git a/code/foundation/src/core/game.h b/code/foundation/src/core/game.h index 6109237..bd68ea3 100644 --- a/code/foundation/src/core/game.h +++ b/code/foundation/src/core/game.h @@ -26,6 +26,7 @@ void game_update(); void game_render(); void game_player_joined(uint64_t ent); void game_player_departed(uint64_t ent); +void game_player_died(uint64_t ent); void game_client_receive_code(pkt_send_code data); // base methods called from games diff --git a/code/foundation/src/models/components.h b/code/foundation/src/models/components.h index 04b4e7d..b9ce635 100644 --- a/code/foundation/src/models/components.h +++ b/code/foundation/src/models/components.h @@ -106,6 +106,10 @@ typedef struct { typedef struct { char _unused; } Dead; +typedef struct { + uint16_t timer; +} Respawn; + typedef struct { float amt; } HealthRegen; @@ -277,6 +281,7 @@ typedef struct { X(ClientInfo)\ X(Health)\ X(Dead)\ + X(Respawn)\ X(HealthRegen)\ X(HealDelay)\ X(Mob)\ diff --git a/code/foundation/src/models/prefabs/prefabs_list.c b/code/foundation/src/models/prefabs/prefabs_list.c index a425bca..9e885db 100644 --- a/code/foundation/src/models/prefabs/prefabs_list.c +++ b/code/foundation/src/models/prefabs/prefabs_list.c @@ -121,9 +121,6 @@ uint64_t storage_spawn(void) { uint64_t mob_spawn(void) { ecs_entity_t e = entity_spawn(EKIND_MONSTER); - Health *hp = ecs_get_mut(world_ecs(), e, Health); - hp->max_hp = hp->hp = 100.0f; - ecs_add(world_ecs(), e, Mob); ecs_set(world_ecs(), e, Health, { 60, 60, 0 }); ecs_set(world_ecs(), e, PhysicsBody, { .kind = PHYS_AABB, .mass = 1.0f }); diff --git a/code/foundation/src/systems/modules/system_health.c b/code/foundation/src/systems/modules/system_health.c index d98203d..8c74a65 100644 --- a/code/foundation/src/systems/modules/system_health.c +++ b/code/foundation/src/systems/modules/system_health.c @@ -50,6 +50,7 @@ void OnDead(ecs_iter_t *it) { if (ci) { pkt_notification_send(0, 0, "Someone died!", zpl_bprintf("Player %d has died!", it->entities[i])); + game_player_died(it->entities[i]); } if (pi) { diff --git a/code/foundation/src/systems/modules/system_vehicle.c b/code/foundation/src/systems/modules/system_vehicle.c index e3189dd..90a0309 100644 --- a/code/foundation/src/systems/modules/system_vehicle.c +++ b/code/foundation/src/systems/modules/system_vehicle.c @@ -110,6 +110,14 @@ void VehicleHandling(ecs_iter_t *it) { car->steer = zpl_lerp(car->steer, 0.0f, safe_dt(it)*game_rules.vehicle_steer_revert); car->steer += (in->x * game_rules.vehicle_steer * steer_mod)*safe_dt(it); car->steer = zpl_clamp(car->steer, -60.0f, 60.0f); + + // if (in->x != 0) { + // // Add a sideways velocity to the car. This will make the car move sideways, + // // giving the appearance of a drift. The actual amount of sideways velocity + // // will need to be fine-tuned. + // v[i].x += in->x * steer_mod * -zpl_sin(car->heading) * car->force; + // v[i].y += in->x * steer_mod * zpl_cos(car->heading) * car->force; + // } } } diff --git a/code/foundation/src/world/world.c b/code/foundation/src/world/world.c index 27c5c8f..a2313e1 100644 --- a/code/foundation/src/world/world.c +++ b/code/foundation/src/world/world.c @@ -320,6 +320,7 @@ void world_setup_ecs(void) { ECS_IMPORT(world.ecs, Components); ECS_IMPORT(world.ecs, Systems); world.ecs_update = ecs_query_new(world.ecs, "components.ClientInfo, components.Position"); + world.ecs_alive_player = ecs_query_new(world.ecs, "components.ClientInfo, components.Position, !components.Dead"); world.ecs_clientinfo = ecs_query_new(world.ecs, "components.ClientInfo"); world.ecs_layeroverriden = ecs_query_new(world.ecs, "components.StreamLayerOverride"); } @@ -531,6 +532,10 @@ ecs_query_t* world_ecs_player(void) { return world.ecs_update; } +ecs_query_t* world_ecs_alive_player(void) { + return world.ecs_alive_player; +} + ecs_query_t* world_ecs_clientinfo(void) { return world.ecs_clientinfo; } diff --git a/code/foundation/src/world/world.h b/code/foundation/src/world/world.h index aa698a6..c6b3fad 100644 --- a/code/foundation/src/world/world.h +++ b/code/foundation/src/world/world.h @@ -52,6 +52,7 @@ typedef struct { ecs_world_t *ecs; ecs_world_t *ecs_stage; ecs_query_t *ecs_update; + ecs_query_t *ecs_alive_player; ecs_query_t *ecs_clientinfo; ecs_query_t *ecs_layeroverriden; ecs_entity_t *chunk_mapping; @@ -72,6 +73,7 @@ uint32_t world_buf(block_id const **ptr, uint32_t *width); uint32_t world_seed(void); ecs_world_t *world_ecs(void); ecs_query_t *world_ecs_player(void); +ecs_query_t *world_ecs_alive_player(void); ecs_query_t *world_ecs_clientinfo(void); void world_set_stage(ecs_world_t *ecs); librg_world *world_tracker(void); diff --git a/code/games/minimal/src/game.c b/code/games/minimal/src/game.c index 65ea93d..bf19fa6 100644 --- a/code/games/minimal/src/game.c +++ b/code/games/minimal/src/game.c @@ -21,6 +21,10 @@ void game_player_departed(uint64_t ent) { } +void game_player_died(uint64_t ent) { + +} + void game_client_receive_code(pkt_send_code data) { } \ No newline at end of file diff --git a/code/games/sandbox/src/game.c b/code/games/sandbox/src/game.c index 99bc070..1ad095c 100644 --- a/code/games/sandbox/src/game.c +++ b/code/games/sandbox/src/game.c @@ -22,6 +22,10 @@ void game_player_departed(uint64_t ent) { } +void game_player_died(uint64_t ent) { + +} + void game_client_receive_code(pkt_send_code data) { } \ No newline at end of file diff --git a/code/games/survival/src/game.c b/code/games/survival/src/game.c index d904497..5ee085a 100644 --- a/code/games/survival/src/game.c +++ b/code/games/survival/src/game.c @@ -19,12 +19,60 @@ static ecs_query_t *ecs_pawn_query = NULL; #include "system_mob.c" #include "system_weapon.c" +#define PLAYER_RESPAWN_BLAST_FORCE 1200.0f + +void PlayerRespawn(ecs_iter_t *it) { + Respawn *r = ecs_field(it, Respawn, 1); + Input *in = ecs_field(it, Input, 2); + Sprite *s = ecs_field(it, Sprite, 3); + Position *p = ecs_field(it, Position, 4); + Health *h = ecs_field(it, Health, 5); + + for (int i = 0; i < it->count; i++) { + if (r[i].timer > 0) { + TICK_VAR(r[i].timer); + continue; + } + + ecs_remove(it->world, it->entities[i], Respawn); + ecs_remove(it->world, it->entities[i], Dead); + in[i].is_blocked = 0; + s[i].spritesheet = 0; + h[i].hp = h[i].max_hp; + + size_t ents_count; + int64_t *ents = world_chunk_query_entities(it->entities[i], &ents_count, 2); + + for (size_t j = 0; j < ents_count; j++) { + uint64_t ent_id = ents[j]; + + if (!ecs_get(it->world, ent_id, Mob) || ecs_get(it->world, ent_id, Dead)) { + continue; + } + + const Position *p2 = ecs_get(it->world, ent_id, Position); + Velocity *v = ecs_get_mut(it->world, ent_id, Velocity); + float dx = p2->x - p[i].x; + float dy = p2->y - p[i].y; + float range = zpl_sqrt(dx*dx + dy*dy); + if (range <= 5*WORLD_BLOCK_SIZE) { + Health *hp = ecs_get_mut(it->world, ent_id, Health); + hp->dmg += hp->max_hp/2.0f; + + v->x += (dx/range)*PLAYER_RESPAWN_BLAST_FORCE; + v->y += (dy/range)*PLAYER_RESPAWN_BLAST_FORCE; + } + } + } +} + void mob_systems(ecs_world_t *ecs) { ECS_SYSTEM_TICKED_EX(ecs, MobDetectPlayers, EcsPostUpdate, 100.0f, components.Position, components.Mob, !components.Dead); ECS_SYSTEM(ecs, MobMovement, EcsPostUpdate, components.Velocity, components.Position, components.MobHuntPlayer, !components.Dead); ECS_SYSTEM_TICKED(ecs, MobMeleeAtk, EcsPostUpdate, components.Position, components.Mob, components.MobHuntPlayer, components.MobMelee, !components.Dead); ECS_SYSTEM_TICKED(ecs, MobDespawnDead, EcsPostUpdate, components.Mob, components.Dead); - ECS_SYSTEM_TICKED(ecs, MobSpawner, EcsPostUpdate, components.Input, components.Position); + ECS_SYSTEM_TICKED(ecs, MobSpawner, EcsPostUpdate, components.Input, components.Position, !components.Dead); + ECS_SYSTEM_TICKED(ecs, PlayerRespawn, EcsPostUpdate, components.Respawn, components.Input, components.Sprite, components.Position, components.Health); //NOTE(DavoSK): weapons ecs_mobpos_query = ecs_query_new(world_ecs(), "components.Mob, components.Position, components.Health, components.Velocity, !components.Dead"); @@ -51,6 +99,10 @@ void game_setup_ecs() { mob_systems(world_ecs()); } +void game_player_departed(uint64_t ent) { + +} + void game_player_joined(uint64_t ent) { notification_push("test1", "Hello World!"); @@ -63,8 +115,14 @@ void game_player_joined(uint64_t ent) { }); } -void game_player_departed(uint64_t ent) { - +void game_player_died(uint64_t ent) { + Sprite *spr = ecs_get_mut(world_ecs(), ent, Sprite); + Velocity *v = ecs_get_mut(world_ecs(), ent, Velocity); + spr->frame = 3 + (rand()%5); + spr->spritesheet = 69; /*special code*/ + *v = (Velocity){0.0f, 0.0f}; + ecs_remove(world_ecs(), ent, PhysicsBody); + ecs_set(world_ecs(), ent, Respawn, { 100 }); } void game_client_receive_code(pkt_send_code data) { diff --git a/code/games/survival/src/main.c b/code/games/survival/src/main.c index 93025d1..d5be192 100644 --- a/code/games/survival/src/main.c +++ b/code/games/survival/src/main.c @@ -27,14 +27,14 @@ ZPL_DIAGNOSTIC_POP /* TODO - - monster spawner - - the longer we survive, the more and stronger enemies we spawn + + monster spawner + + the longer we survive, the more and stronger enemies we spawn - player grows HP by leveling up - XP increases by killing mobs - player can pick an "ability" upon reaching level milestones - abilities: armor/shield, TODO ... - - enemies damage player when close to him in ticks (damage effects, ...) - - basic projectile pooling (flecs) + + enemies damage player when close to him in ticks (damage effects, ...) + + basic projectile pooling (flecs) - somewhat believable world gen, small hamlets with cols, etc */ diff --git a/code/games/survival/src/platform.c b/code/games/survival/src/platform.c index 6970200..fef43dc 100644 --- a/code/games/survival/src/platform.c +++ b/code/games/survival/src/platform.c @@ -109,6 +109,7 @@ void platform_render() { debug_draw(); +#if defined(_DEBUG) if (nk_begin(game_ui, "Spritesheet Viewer", nk_rect(460, 100, 800, 600), NK_WINDOW_BORDER|NK_WINDOW_MOVABLE|NK_WINDOW_SCALABLE|NK_WINDOW_TITLE)) { @@ -117,6 +118,7 @@ void platform_render() { } notification_draw(); +#endif game_draw_ui(); } EndDrawing(); diff --git a/code/games/survival/src/renderer.c b/code/games/survival/src/renderer.c index c8e6921..aafb735 100644 --- a/code/games/survival/src/renderer.c +++ b/code/games/survival/src/renderer.c @@ -23,7 +23,9 @@ void DrawNametag(const char* name, uint64_t key, entity_view *data, float x, flo const char *title = TextFormat("%s %llu", name, key); float title_w = MeasureTextEco(title, font_size, font_spacing); DrawRectangleEco(x-title_w/2.f-title_bg_offset/2.f, y-size-font_size-fixed_title_offset, title_w+title_bg_offset, font_size, ColorAlpha(BLACK, data->tran_time)); - DrawRectangleEco(x-title_w/2.f-title_bg_offset/2.f, y-size-fixed_title_offset, title_w*health+title_bg_offset, font_size*0.2f, ColorAlpha(RED, data->tran_time)); + if (health > 0.0f && health <= 1.0f) { + DrawRectangleEco(x-title_w/2.f-title_bg_offset/2.f, y-size-fixed_title_offset, title_w*health+title_bg_offset, font_size*0.2f, ColorAlpha(RED, data->tran_time)); + } DrawTextEco(title, x-title_w/2.f, y-size-font_size-fixed_title_offset, font_size, ColorAlpha(RAYWHITE, data->tran_time), font_spacing); } @@ -46,7 +48,8 @@ void renderer_draw_entry(uint64_t key, entity_view *data, game_world_render_entr tex.texture.height *= (int32_t)scale; DrawTextureRec(tex.texture, (Rectangle){0, 0, size, -size}, (Vector2){x, y}, ColorAlpha(WHITE, data->tran_time)); } else { - DrawTextureRec(GetBlockImage(entry->blk_id), ASSET_SRC_RECT(), (Vector2){entry->x-(WORLD_BLOCK_SIZE/2), entry->y-(WORLD_BLOCK_SIZE/2)}, ColorAlpha(WHITE, data->tran_time)); + Texture2D tex = GetBlockImage(entry->blk_id); + DrawTextureRec(tex, ASSET_SRC_RECT_TEX(tex.width, tex.height), (Vector2){entry->x-tex.width/2.0f, entry->y-(tex.height-WORLD_BLOCK_SIZE/2)}, ColorAlpha(WHITE, data->tran_time)); } }break; case EKIND_VEHICLE: { @@ -87,9 +90,7 @@ void renderer_draw_entry(uint64_t key, entity_view *data, game_world_render_entr case EKIND_MONSTER: { float x = data->x; float y = data->y; - //DrawCircleEco(x, y, size, ColorAlpha(PINK, data->tran_time)); - // DrawTextureRec(GetSpriteTexture2D(assets_find(data->frame)), ASSET_SRC_RECT(), (Vector2){data->x-(WORLD_BLOCK_SIZE/2), data->y-(WORLD_BLOCK_SIZE/2)}, ColorAlpha(WHITE, data->tran_time)); - DrawSpriteEco(&main_sprite_sheet, data->frame, x, y, 0.0f, 2.0f, WHITE); + DrawSpriteEco(&main_sprite_sheet, data->frame, x, y, 0.0f, 2.0f, ColorAlpha(WHITE, data->tran_time)); float health = (data->hp / data->max_hp); @@ -105,7 +106,11 @@ void renderer_draw_entry(uint64_t key, entity_view *data, game_world_render_entr DrawNametag("Player", key, data, x, y-16); //DrawTextureRec(GetSpriteTexture2D(assets_find(ASSET_PLAYER)), ASSET_SRC_RECT(), (Vector2){data->x-(WORLD_BLOCK_SIZE/2), data->y-(WORLD_BLOCK_SIZE/2)}, ColorAlpha(WHITE, data->tran_time)); //DrawCircleEco(x, y, size, ColorAlpha(YELLOW, data->tran_time)); - DrawSpriteEco(test_player_anim.spritesheet, TickSpriteAnimation(&test_player_anim), x, y, 0.0f, 2.0f, WHITE); + + if (data->spritesheet == 69) + DrawSpriteEco(&main_sprite_sheet, data->frame, x, y, 0.0f, 2.0f, ColorAlpha(WHITE, data->tran_time)); + else + DrawSpriteEco(test_player_anim.spritesheet, TickSpriteAnimation(&test_player_anim), x, y, 0.0f, 2.0f, WHITE); //if (data->has_items && !data->inside_vehicle) { // float ix = data->x; diff --git a/code/games/survival/src/system_mob.c b/code/games/survival/src/system_mob.c index 994be43..1e6090f 100644 --- a/code/games/survival/src/system_mob.c +++ b/code/games/survival/src/system_mob.c @@ -1,8 +1,8 @@ #define MOB_SPAWN_DELAY (uint16_t)(20*1.5f) #define MOB_SPAWN_PERCENTAGE_FROM_MAX 0.05f #define MOB_INITIAL_MAX 50 -#define MOB_GROWTH_FACTOR 5.0f -#define MOB_GROWTH_CONTROL 50.0f +#define MOB_GROWTH_FACTOR 0.65f +#define MOB_GROWTH_CONTROL 1.17f #define MOB_SPAWN_DIST 12*WORLD_BLOCK_SIZE; #define MOB_MAX_SPAWN_TRIES 5 @@ -12,8 +12,7 @@ static uint16_t mob_spawn_timer = MOB_SPAWN_DELAY; static int16_t player_spawn_counter = -1; void recalc_max_mobs() { - max_mobs = (uint64_t)zpl_round(MOB_INITIAL_MAX + MOB_GROWTH_FACTOR * zpl_exp((float)mob_kills / MOB_GROWTH_CONTROL)); - zpl_printf("Max mobs: %d\n", max_mobs); + max_mobs = (uint64_t)(MOB_INITIAL_MAX + MOB_GROWTH_FACTOR * zpl_pow((float)mob_kills, MOB_GROWTH_CONTROL)); } void MobDetectPlayers(ecs_iter_t *it) { @@ -23,7 +22,7 @@ void MobDetectPlayers(ecs_iter_t *it) { float closest_ent_dist = ZPL_F32_MAX; uint64_t closest_ent = 0; - ecs_iter_t pit = ecs_query_iter(world_ecs(), world_ecs_player()); + ecs_iter_t pit = ecs_query_iter(world_ecs(), world_ecs_alive_player()); while (ecs_query_next(&pit)) { Position *p2 = ecs_field(&pit, Position, 2); @@ -54,6 +53,10 @@ void MobMovement(ecs_iter_t *it) { for (int i = 0; i < it->count; i++) { const Position *p2 = ecs_get(it->world, m->plr, Position); + if (p2 == NULL) { + ecs_remove(it->world, it->entities[i], MobHuntPlayer); + continue; + } zpl_vec2 pos1 = { .x = p[i].x, .y = p[i].y }; zpl_vec2 pos2 = { .x = p2->x, .y = p2->y }; zpl_vec2 dir; @@ -71,6 +74,7 @@ void MobMovement(ecs_iter_t *it) { #define MOB_MELEE_DMG 8.5f #define MOB_ATK_DELAY 10 #define MOB_DESPAWN_TIMER 20*60*5 +#define MOB_HIT_FORCE_PUSH 250.0f void MobMeleeAtk(ecs_iter_t *it) { Position *p = ecs_field(it, Position, 1); @@ -83,15 +87,25 @@ void MobMeleeAtk(ecs_iter_t *it) { continue; } + if (ecs_get(it->world, m->plr, Dead)) { + ecs_remove(it->world, it->entities[i], MobHuntPlayer); + continue; + } + const Position *p2 = ecs_get(it->world, m->plr, Position); + Velocity *v2 = ecs_get_mut(it->world, m->plr, Velocity); + float dx = p2->x - p[i].x; float dy = p2->y - p[i].y; float range = (dx*dx + dy*dy); if (range < MOB_MELEE_DIST) { + float dd = zpl_sqrt(range); Health *health = ecs_get_mut(it->world, m->plr, Health); health->dmg += MOB_MELEE_DMG; mob[i].atk_delay = MOB_ATK_DELAY; + v2->x += (dx/dd)*MOB_HIT_FORCE_PUSH; + v2->y += (dy/dd)*MOB_HIT_FORCE_PUSH; } } } diff --git a/code/games/survival/src/worldgen.c b/code/games/survival/src/worldgen.c index e46d0c9..5151385 100644 --- a/code/games/survival/src/worldgen.c +++ b/code/games/survival/src/worldgen.c @@ -34,6 +34,7 @@ block_id worldgen_biome_find(uint32_t biome, uint32_t kind) { return blocks_find(asset); } +#if 0 int32_t worldgen_build(world_data *wld) { // TODO(zaklaus): pass world as an arg instead world = wld; @@ -67,3 +68,58 @@ int32_t worldgen_build(world_data *wld) { return WORLD_ERROR_NONE; } +#else +int32_t worldgen_build(world_data *wld) { + // TODO(zaklaus): pass world as an arg instead + world = wld; + + // TODO: perform world gen + // atm, we will fill the world with ground and surround it by walls + block_id wall_id = worldgen_biome_find(BLOCK_BIOME_DEV, BLOCK_KIND_WALL); + block_id grnd_id = worldgen_biome_find(BLOCK_BIOME_DEV, BLOCK_KIND_GROUND); + block_id dirt_id = worldgen_biome_find(BLOCK_BIOME_DEV, BLOCK_KIND_DIRT); + block_id watr_id = worldgen_biome_find(BLOCK_BIOME_DEV, BLOCK_KIND_WATER); + block_id lava_id = worldgen_biome_find(BLOCK_BIOME_DEV, BLOCK_KIND_LAVA); + block_id tree_id = blocks_find(ASSET_TREE); + + srand(world->seed); + + // walls + world_fill_rect(world->data, wall_id, 0, 0, world->dim, world->dim, NULL); + + // ground + world_fill_rect(world->data, grnd_id, 1, 1, world->dim-2, world->dim-2, NULL); + world_fill_rect(world->data, dirt_id, 1, 1, world->dim-2, world->dim-2, shaper_noise05); + world_fill_rect(world->outer_data, tree_id, 1, 1, world->dim-2, world->dim-2, shaper_noise01b); + + // water +#if 1 + for (int i=0; iouter_data, watr_id, RAND_RANGE(0, world->dim), RAND_RANGE(0, world->dim), 4+RAND_RANGE(0,3), 4+RAND_RANGE(0,3), 0.5f, 0.5f, shaper_noise80); + } +#endif + + // ice rink +#if 0 + world_fill_rect_anchor(world->data, watr_id, 450, 125, 10, 10, 0.0f, 0.0f, NULL); +#endif + + // lava +#if 1 + for (int i=0; idata, lava_id, RAND_RANGE(0, world->dim), RAND_RANGE(0, world->dim), 4+RAND_RANGE(0,3), 4+RAND_RANGE(0,3), 0.5f, 0.5f, shaper_noise80); + } +#endif + + + // hills +#if 1 + const uint32_t HILLS_SIZE = 21; + for (int i=0; idata, wall_id, RAND_RANGE(0, world->dim), RAND_RANGE(0, world->dim), RAND_RANGE(0,HILLS_SIZE), RAND_RANGE(0,HILLS_SIZE), 0.5f, 0.5f, shaper_noise50); + } +#endif + + return WORLD_ERROR_NONE; +} +#endif \ No newline at end of file diff --git a/win/survival_package_web.bat b/win/survival_package_web.bat new file mode 100644 index 0000000..c34e25a --- /dev/null +++ b/win/survival_package_web.bat @@ -0,0 +1,14 @@ +@echo off + +rem build web first +call web.bat + +if not %ERRORLEVEL% == 0 exit /B 1 +@rd /S /Q pkg +mkdir pkg +copy ..\build_web\survival.* pkg +copy ..\build_web\survival.html pkg\index.html + +IF NOT "%1"=="SKIP_DEPLOY" ( + butler push pkg zaklaus/zplsurvival:html-latest +)