Compare commits
No commits in common. "a8604946f6c3611777902a6cccbd8bf42e6dcc6f" and "11fc686caae9986df96cccec83e9eaa808dc1291" have entirely different histories.
a8604946f6
...
11fc686caa
107
build.zig
107
build.zig
@ -1,122 +1,17 @@
|
||||
const std = @import("std");
|
||||
const sokol = @import("sokol");
|
||||
|
||||
pub fn build(b: *std.Build) !void {
|
||||
pub fn build(b: *std.Build) void {
|
||||
const target = b.standardTargetOptions(.{});
|
||||
const optimize = b.standardOptimizeOption(.{});
|
||||
|
||||
const has_imgui = b.option(bool, "imgui", "ImGui integration") orelse (optimize == .Debug);
|
||||
const has_tracy = b.option(bool, "tracy", "Tracy integration") orelse (optimize == .Debug);
|
||||
|
||||
const exe = b.addExecutable(.{
|
||||
.name = "game_2025_12_13",
|
||||
.root_module = b.createModule(.{
|
||||
.root_source_file = b.path("src/main.zig"),
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
.link_libc = true
|
||||
}),
|
||||
});
|
||||
const exe_mod = exe.root_module;
|
||||
|
||||
const tracy_dependency = b.dependency("tracy", .{
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
.tracy_enable = has_tracy,
|
||||
.tracy_only_localhost = true
|
||||
});
|
||||
exe_mod.linkLibrary(tracy_dependency.artifact("tracy"));
|
||||
exe_mod.addImport("tracy", tracy_dependency.module("tracy"));
|
||||
|
||||
const stb_dependency = b.dependency("stb", .{});
|
||||
exe_mod.addIncludePath(stb_dependency.path("."));
|
||||
|
||||
const sokol_c_dependency = b.dependency("sokol_c", .{});
|
||||
exe_mod.addIncludePath(sokol_c_dependency.path("util"));
|
||||
|
||||
const fontstash_dependency = b.dependency("fontstash", .{});
|
||||
exe_mod.addIncludePath(fontstash_dependency.path("src"));
|
||||
|
||||
const libxml2_dependency = b.dependency("libxml2", .{
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
.linkage = .static,
|
||||
});
|
||||
|
||||
{
|
||||
const libtmx_dependency = b.dependency("libtmx", .{});
|
||||
const libtmx = b.addLibrary(.{
|
||||
.name = "tmx",
|
||||
.root_module = b.createModule(.{
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
.link_libc = true
|
||||
}),
|
||||
});
|
||||
libtmx.installHeader(libtmx_dependency.path("src/tmx.h"), "tmx.h");
|
||||
libtmx.root_module.addCSourceFiles(.{
|
||||
.root = libtmx_dependency.path("src"),
|
||||
.files = &.{
|
||||
"tmx.c",
|
||||
"tmx_utils.c",
|
||||
"tmx_err.c",
|
||||
"tmx_xml.c",
|
||||
"tmx_mem.c",
|
||||
"tmx_hash.c"
|
||||
},
|
||||
.flags = &.{
|
||||
"-fno-delete-null-pointer-checks"
|
||||
}
|
||||
});
|
||||
libtmx.linkLibrary(libxml2_dependency.artifact("xml"));
|
||||
|
||||
exe_mod.linkLibrary(libtmx);
|
||||
}
|
||||
|
||||
const sokol_dependency = b.dependency("sokol", .{
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
.with_sokol_imgui = has_imgui
|
||||
});
|
||||
|
||||
exe_mod.addImport("sokol", sokol_dependency.module("sokol"));
|
||||
exe_mod.linkLibrary(sokol_dependency.artifact("sokol_clib"));
|
||||
|
||||
var cflags_buffer: [64][]const u8 = undefined;
|
||||
var cflags = std.ArrayListUnmanaged([]const u8).initBuffer(&cflags_buffer);
|
||||
switch (sokol.resolveSokolBackend(.auto, target.result)) {
|
||||
.d3d11 => try cflags.appendBounded("-DSOKOL_D3D11"),
|
||||
.metal => try cflags.appendBounded("-DSOKOL_METAL"),
|
||||
.gl => try cflags.appendBounded("-DSOKOL_GLCORE"),
|
||||
.gles3 => try cflags.appendBounded("-DSOKOL_GLES3"),
|
||||
.wgpu => try cflags.appendBounded("-DSOKOL_WGPU"),
|
||||
else => @panic("unknown sokol backend"),
|
||||
}
|
||||
exe_mod.addIncludePath(b.path("src/libs"));
|
||||
exe_mod.addCSourceFile(.{
|
||||
.file = b.path("src/libs/sokol_fontstash_impl.c"),
|
||||
.flags = cflags.items
|
||||
});
|
||||
|
||||
exe_mod.addCSourceFile(.{
|
||||
.file = b.path("src/libs/stb_image.c"),
|
||||
.flags = &.{}
|
||||
});
|
||||
|
||||
if (has_imgui) {
|
||||
if (b.lazyDependency("cimgui", .{
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
})) |cimgui_dependency| {
|
||||
sokol_dependency.artifact("sokol_clib").addIncludePath(cimgui_dependency.path("src"));
|
||||
exe_mod.addImport("cimgui", cimgui_dependency.module("cimgui"));
|
||||
}
|
||||
}
|
||||
|
||||
var options = b.addOptions();
|
||||
options.addOption(bool, "has_imgui", has_imgui);
|
||||
options.addOption(bool, "has_tracy", has_tracy);
|
||||
exe_mod.addOptions("options", options);
|
||||
|
||||
b.installArtifact(exe);
|
||||
|
||||
|
||||
103
build.zig.zon
103
build.zig.zon
@ -1,46 +1,81 @@
|
||||
.{
|
||||
// This is the default name used by packages depending on this one. For
|
||||
// example, when a user runs `zig fetch --save <url>`, this field is used
|
||||
// as the key in the `dependencies` table. Although the user can choose a
|
||||
// different name, most users will stick with this provided value.
|
||||
//
|
||||
// It is redundant to include "zig" in this name because it is already
|
||||
// within the Zig package namespace.
|
||||
.name = .game_2025_12_13,
|
||||
// This is a [Semantic Version](https://semver.org/).
|
||||
// In a future version of Zig it will be used for package deduplication.
|
||||
.version = "0.0.0",
|
||||
.fingerprint = 0x5704f7ae3ffdd7f8,
|
||||
// Together with name, this represents a globally unique package
|
||||
// identifier. This field is generated by the Zig toolchain when the
|
||||
// package is first created, and then *never changes*. This allows
|
||||
// unambiguous detection of one package being an updated version of
|
||||
// another.
|
||||
//
|
||||
// When forking a Zig project, this id should be regenerated (delete the
|
||||
// field and run `zig build`) if the upstream project is still maintained.
|
||||
// Otherwise, the fork is *hostile*, attempting to take control over the
|
||||
// original project's identity. Thus it is recommended to leave the comment
|
||||
// on the following line intact, so that it shows up in code reviews that
|
||||
// modify the field.
|
||||
.fingerprint = 0x5704f7ae3ffdd7f8, // Changing this has security and trust implications.
|
||||
// Tracks the earliest Zig version that the package considers to be a
|
||||
// supported use case.
|
||||
.minimum_zig_version = "0.15.2",
|
||||
// This field is optional.
|
||||
// Each dependency must either provide a `url` and `hash`, or a `path`.
|
||||
// `zig build --fetch` can be used to fetch all dependencies of a package, recursively.
|
||||
// Once all dependencies are fetched, `zig build` no longer requires
|
||||
// internet connectivity.
|
||||
.dependencies = .{
|
||||
.sokol = .{
|
||||
.url = "git+https://github.com/floooh/sokol-zig.git#1e233203b41893a8bf9c1c91933eba98204b6ed8",
|
||||
.hash = "sokol-0.1.0-pb1HK42FNgDb5sqnsadiO2qabkfUX8jXP_DheOZGcD1W",
|
||||
},
|
||||
.cimgui = .{
|
||||
.url = "git+https://github.com/floooh/dcimgui.git#33c99ef426b68030412b5a4b11487a23da9d4f13",
|
||||
.hash = "cimgui-0.1.0-44ClkQRJlABdFMKRqIG8KDD6jy1eQbgPO335NziPYjmL",
|
||||
.lazy = true,
|
||||
},
|
||||
.tracy = .{
|
||||
.url = "git+https://github.com/sagehane/zig-tracy.git#80933723efe9bf840fe749b0bfc0d610f1db1669",
|
||||
.hash = "zig_tracy-0.0.5-aOIqsX1tAACKaRRB-sraMLuNiMASXi_y-4FtRuw4cTpx",
|
||||
},
|
||||
.stb = .{
|
||||
.url = "git+https://github.com/nothings/stb.git#f1c79c02822848a9bed4315b12c8c8f3761e1296",
|
||||
.hash = "N-V-__8AABQ7TgCnPlp8MP4YA8znrjd6E-ZjpF1rvrS8J_2I",
|
||||
},
|
||||
.sokol_c = .{
|
||||
.url = "git+https://github.com/floooh/sokol.git#c66a1f04e6495d635c5e913335ab2308281e0492",
|
||||
.hash = "N-V-__8AAC3eYABB1DVLb4dkcEzq_xVeEZZugVfQ6DoNQBDN",
|
||||
},
|
||||
.fontstash = .{
|
||||
.url = "git+https://github.com/memononen/fontstash.git#b5ddc9741061343740d85d636d782ed3e07cf7be",
|
||||
.hash = "N-V-__8AAA9xHgAxdLYPmlNTy6qzv9IYqiIePEHQUOPWYQ_6",
|
||||
},
|
||||
.libtmx = .{
|
||||
.url = "git+https://github.com/baylej/tmx.git#11ffdcdc9bd65669f1a8dbd3a0362a324dda2e0c",
|
||||
.hash = "N-V-__8AAKQvBQCTT3Q6_we7vTVX-MkAWDZ91YkUev040IRo",
|
||||
},
|
||||
.libxml2 = .{
|
||||
.url = "git+https://github.com/allyourcodebase/libxml2.git?ref=2.14.3-4#86c4742a9becd6c86dc79180f806ed344fd2a727",
|
||||
.hash = "libxml2-2.14.3-4-qHdjhn9FAACpyisv_5DDFVQlegox6QE3mTpdlr44RcbT",
|
||||
},
|
||||
// See `zig fetch --save <url>` for a command-line interface for adding dependencies.
|
||||
//.example = .{
|
||||
// // When updating this field to a new URL, be sure to delete the corresponding
|
||||
// // `hash`, otherwise you are communicating that you expect to find the old hash at
|
||||
// // the new URL. If the contents of a URL change this will result in a hash mismatch
|
||||
// // which will prevent zig from using it.
|
||||
// .url = "https://example.com/foo.tar.gz",
|
||||
//
|
||||
// // This is computed from the file contents of the directory of files that is
|
||||
// // obtained after fetching `url` and applying the inclusion rules given by
|
||||
// // `paths`.
|
||||
// //
|
||||
// // This field is the source of truth; packages do not come from a `url`; they
|
||||
// // come from a `hash`. `url` is just one of many possible mirrors for how to
|
||||
// // obtain a package matching this `hash`.
|
||||
// //
|
||||
// // Uses the [multihash](https://multiformats.io/multihash/) format.
|
||||
// .hash = "...",
|
||||
//
|
||||
// // When this is provided, the package is found in a directory relative to the
|
||||
// // build root. In this case the package's hash is irrelevant and therefore not
|
||||
// // computed. This field and `url` are mutually exclusive.
|
||||
// .path = "foo",
|
||||
//
|
||||
// // When this is set to `true`, a package is declared to be lazily
|
||||
// // fetched. This makes the dependency only get fetched if it is
|
||||
// // actually used.
|
||||
// .lazy = false,
|
||||
//},
|
||||
},
|
||||
// Specifies the set of files and directories that are included in this package.
|
||||
// Only files and directories listed here are included in the `hash` that
|
||||
// is computed for this package. Only files listed here will remain on disk
|
||||
// when using the zig package manager. As a rule of thumb, one should list
|
||||
// files required for compilation plus any license(s).
|
||||
// Paths are relative to the build root. Use the empty string (`""`) to refer to
|
||||
// the build root itself.
|
||||
// A directory listed here means that all files within, recursively, are included.
|
||||
.paths = .{
|
||||
"build.zig",
|
||||
"build.zig.zon",
|
||||
"src",
|
||||
// For example...
|
||||
//"LICENSE",
|
||||
//"README.md",
|
||||
},
|
||||
}
|
||||
|
||||
@ -1,23 +0,0 @@
|
||||
|
||||
|
||||
Micro Roguelike (1.4)
|
||||
|
||||
Created/distributed by Kenney (www.kenney.nl)
|
||||
Creation date: 01-11-2021
|
||||
|
||||
------------------------------
|
||||
|
||||
License: (Creative Commons Zero, CC0)
|
||||
http://creativecommons.org/publicdomain/zero/1.0/
|
||||
|
||||
This content is free to use in personal, educational and commercial projects.
|
||||
|
||||
Support us by crediting Kenney or www.kenney.nl (this is not mandatory)
|
||||
|
||||
------------------------------
|
||||
|
||||
Donate: http://support.kenney.nl
|
||||
Patreon: http://patreon.com/kenney/
|
||||
|
||||
Follow on Twitter for updates:
|
||||
http://twitter.com/KenneyNL
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 2.6 KiB |
@ -1,93 +0,0 @@
|
||||
Copyright 2011 The Roboto Project Authors (https://github.com/googlefonts/roboto-classic)
|
||||
|
||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
https://openfontlicense.org
|
||||
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font creation
|
||||
efforts of academic and linguistic communities, and to provide a free and
|
||||
open framework in which fonts may be shared and improved in partnership
|
||||
with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply
|
||||
to any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software components as
|
||||
distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||
or substituting -- in part or in whole -- any of the components of the
|
||||
Original Version, by changing formats or by porting the Font Software to a
|
||||
new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||
redistribute, and sell modified and unmodified copies of the Font
|
||||
Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components,
|
||||
in Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the corresponding
|
||||
Copyright Holder. This restriction only applies to the primary font name as
|
||||
presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created
|
||||
using the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1,64 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<map version="1.10" tiledversion="1.11.2" orientation="orthogonal" renderorder="right-down" width="20" height="15" tilewidth="8" tileheight="8" infinite="0" nextlayerid="5" nextobjectid="1">
|
||||
<tileset firstgid="1" source="tileset.tsx"/>
|
||||
<layer id="1" name="Actors" width="20" height="15">
|
||||
<data encoding="csv">
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,5,0,0,0,0,0,0,53,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
|
||||
</data>
|
||||
</layer>
|
||||
<layer id="2" name="Walls" width="20" height="15">
|
||||
<properties>
|
||||
<property name="solid" type="bool" value="true"/>
|
||||
</properties>
|
||||
<data encoding="csv">
|
||||
0,0,0,0,0,70,70,70,0,0,0,0,0,0,70,0,0,0,0,0,
|
||||
70,70,0,0,0,0,0,0,0,70,70,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,1,68,2,68,2,2,2,67,2,2,2,4,0,0,0,0,
|
||||
0,0,0,0,17,0,0,0,0,0,0,0,0,0,0,20,0,0,0,0,
|
||||
0,0,0,0,17,0,0,0,0,0,0,0,0,0,0,20,0,70,0,0,
|
||||
0,70,70,0,17,0,0,0,0,0,0,0,0,0,0,20,0,70,70,0,
|
||||
0,0,70,0,17,0,0,0,0,0,0,0,0,0,0,20,0,0,0,0,
|
||||
0,0,0,0,17,0,0,0,0,0,0,0,0,0,0,20,0,0,0,0,
|
||||
0,0,0,0,17,0,0,0,0,0,0,0,0,0,0,20,0,0,0,0,
|
||||
0,0,0,0,17,0,0,0,0,0,0,0,0,0,0,20,0,0,0,0,
|
||||
0,0,0,0,83,2,2,68,2,67,2,2,2,67,2,36,0,0,0,0,
|
||||
0,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,70,0,0,0,0,0,0,0,0,70,70,70,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,70,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,70,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
|
||||
</data>
|
||||
</layer>
|
||||
<layer id="4" name="Decorations" width="20" height="15">
|
||||
<data encoding="csv">
|
||||
0,85,85,0,0,0,0,85,85,85,85,85,85,0,0,0,0,85,85,85,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,85,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,85,
|
||||
0,0,85,0,0,0,0,0,0,0,0,0,0,0,70,0,0,0,0,85,
|
||||
85,0,0,0,0,0,69,0,0,70,0,0,0,70,0,0,0,0,0,85,
|
||||
85,0,0,0,0,0,0,0,0,0,0,0,69,0,0,0,0,0,0,85,
|
||||
85,0,0,0,0,0,0,0,0,69,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,85,0,0,
|
||||
0,85,0,0,0,0,69,0,0,0,70,0,69,0,0,0,0,85,0,0,
|
||||
0,85,0,0,0,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
85,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,85,
|
||||
0,0,0,0,85,0,0,0,0,0,0,0,0,0,85,0,85,0,0,85,
|
||||
85,0,85,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,85,
|
||||
0,0,0,0,0,0,85,85,85,85,85,85,0,0,0,0,0,0,0,85
|
||||
</data>
|
||||
</layer>
|
||||
</map>
|
||||
@ -1,23 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<map version="1.10" tiledversion="1.11.2" orientation="orthogonal" renderorder="right-down" width="20" height="15" tilewidth="8" tileheight="8" infinite="0" nextlayerid="2" nextobjectid="1">
|
||||
<tileset firstgid="1" source="tileset.tsx"/>
|
||||
<layer id="1" name="Tiles" width="20" height="15">
|
||||
<data encoding="csv">
|
||||
0,0,0,0,0,70,70,70,0,0,0,0,0,0,0,0,85,85,0,0,
|
||||
0,85,85,85,0,0,0,70,0,0,1,2,2,2,4,0,0,85,70,85,
|
||||
0,0,0,0,85,85,70,70,0,0,17,0,0,0,20,0,85,85,70,85,
|
||||
0,0,0,85,85,0,0,0,0,0,17,0,53,0,20,0,0,0,70,0,
|
||||
0,0,0,0,70,0,0,0,0,0,17,0,0,0,20,0,0,0,0,0,
|
||||
0,85,0,70,70,0,1,2,2,2,2,2,40,2,2,66,0,0,0,0,
|
||||
0,85,0,0,0,0,17,69,0,5,0,41,91,0,69,20,0,85,0,0,
|
||||
0,85,85,0,0,0,17,69,1,2,2,4,0,0,69,20,0,85,85,0,
|
||||
0,0,0,85,0,0,17,69,17,70,70,49,2,36,0,20,0,0,0,70,
|
||||
0,0,0,85,0,0,17,0,17,0,0,0,0,0,0,20,0,0,70,70,
|
||||
0,0,0,0,0,0,17,0,17,0,33,2,2,2,2,66,0,0,0,0,
|
||||
0,0,85,0,0,0,17,0,17,0,0,0,0,0,0,20,0,0,0,0,
|
||||
85,0,0,0,0,0,17,0,33,2,2,2,2,36,69,20,0,0,85,85,
|
||||
85,0,0,0,0,0,17,70,70,0,0,0,0,69,69,20,0,70,85,0,
|
||||
0,0,85,85,0,0,33,2,2,2,2,2,2,2,2,2,0,70,85,0
|
||||
</data>
|
||||
</layer>
|
||||
</map>
|
||||
@ -1,14 +0,0 @@
|
||||
{
|
||||
"automappingRulesFile": "",
|
||||
"commands": [
|
||||
],
|
||||
"compatibilityVersion": 1100,
|
||||
"extensionsPath": "extensions",
|
||||
"folders": [
|
||||
"."
|
||||
],
|
||||
"properties": [
|
||||
],
|
||||
"propertyTypes": [
|
||||
]
|
||||
}
|
||||
@ -1,61 +0,0 @@
|
||||
{
|
||||
"Map/SizeTest": {
|
||||
"height": 4300,
|
||||
"width": 2
|
||||
},
|
||||
"activeFile": "",
|
||||
"expandedProjectPaths": [
|
||||
],
|
||||
"fileStates": {
|
||||
"first.tmx": {
|
||||
"scale": 4,
|
||||
"selectedLayer": 1,
|
||||
"viewCenter": {
|
||||
"x": 79.5,
|
||||
"y": 71.5
|
||||
}
|
||||
},
|
||||
"fourth.tmx": {
|
||||
"scale": 5.24875,
|
||||
"selectedLayer": 0,
|
||||
"viewCenter": {
|
||||
"x": 82.30531078828292,
|
||||
"y": 78.49487973326983
|
||||
}
|
||||
},
|
||||
"second.tmx": {
|
||||
"scale": 4,
|
||||
"selectedLayer": 2,
|
||||
"viewCenter": {
|
||||
"x": 67.25,
|
||||
"y": 71.5
|
||||
}
|
||||
},
|
||||
"third.tmx": {
|
||||
"scale": 4,
|
||||
"selectedLayer": 0,
|
||||
"viewCenter": {
|
||||
"x": 65.75,
|
||||
"y": 49.5
|
||||
}
|
||||
}
|
||||
},
|
||||
"last.imagePath": "/home/rokas/code/games/game-2025-12-13/src/assets/kenney-micro-roguelike",
|
||||
"map.height": 15,
|
||||
"map.lastUsedFormat": "tmx",
|
||||
"map.tileHeight": 8,
|
||||
"map.tileWidth": 8,
|
||||
"map.width": 20,
|
||||
"openFiles": [
|
||||
],
|
||||
"project": "main.tiled-project",
|
||||
"recentFiles": [
|
||||
"first.tmx"
|
||||
],
|
||||
"textEdit.monospace": true,
|
||||
"tileset.lastUsedFormat": "tsx",
|
||||
"tileset.tileSize": {
|
||||
"height": 8,
|
||||
"width": 8
|
||||
}
|
||||
}
|
||||
@ -1,61 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<map version="1.10" tiledversion="1.11.2" orientation="orthogonal" renderorder="right-down" width="20" height="15" tilewidth="8" tileheight="8" infinite="0" nextlayerid="4" nextobjectid="1">
|
||||
<tileset firstgid="1" source="tileset.tsx"/>
|
||||
<layer id="3" name="Deco" width="20" height="15">
|
||||
<data encoding="csv">
|
||||
0,0,0,85,0,0,0,85,85,85,85,0,0,0,0,0,0,0,85,0,
|
||||
0,0,85,85,0,0,0,0,0,0,0,0,0,0,0,0,0,0,85,85,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,85,0,0,0,0,0,0,0,0,0,0,0,85,0,0,
|
||||
85,0,0,0,85,85,0,0,0,0,0,0,0,0,0,0,0,85,85,0,
|
||||
85,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,85,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,85,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,85,0,0,85,
|
||||
0,0,0,70,69,0,0,0,0,0,0,70,70,0,0,0,0,0,0,0,
|
||||
85,0,0,0,0,0,0,0,69,0,69,0,70,0,0,0,0,0,85,0,
|
||||
85,0,0,70,70,0,0,69,0,0,0,0,69,0,0,0,0,0,85,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,85,85,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,85,85,0,0,0,0,85,85,85,85,85,0,0,0,0,0,85
|
||||
</data>
|
||||
</layer>
|
||||
<layer id="1" name="Actors" width="20" height="15">
|
||||
<data encoding="csv">
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,53,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,40,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,91,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,5,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
|
||||
</data>
|
||||
</layer>
|
||||
<layer id="2" name="Walls" width="20" height="15">
|
||||
<data encoding="csv">
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,70,0,0,0,0,
|
||||
0,0,0,0,70,70,0,0,0,1,2,2,2,4,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,17,0,0,0,20,0,0,0,0,70,0,
|
||||
0,0,70,0,0,0,0,0,0,17,0,0,0,20,0,0,0,0,0,0,
|
||||
0,0,70,0,0,0,0,0,0,17,0,0,0,20,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,65,2,0,2,66,0,0,0,0,0,0,
|
||||
0,70,0,0,70,0,0,0,0,17,0,69,0,20,0,0,0,70,0,0,
|
||||
0,70,0,0,0,0,0,0,0,17,0,69,0,20,0,0,0,70,0,0,
|
||||
0,0,1,2,2,2,2,2,2,50,0,0,0,20,0,0,0,0,0,0,
|
||||
0,0,17,0,0,0,0,0,0,0,0,0,0,20,0,70,0,0,0,0,
|
||||
0,0,17,0,0,0,0,0,0,0,0,0,0,20,0,0,0,0,0,0,
|
||||
0,0,17,0,0,0,0,0,0,0,0,0,0,20,0,0,0,0,0,0,
|
||||
0,0,33,2,2,2,2,2,2,2,2,2,2,36,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,70,0,0,0,0,0,0,70,70,0,0,0,
|
||||
0,0,0,0,0,0,70,70,0,0,0,0,0,0,0,0,0,0,0,0
|
||||
</data>
|
||||
</layer>
|
||||
</map>
|
||||
@ -1,23 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<map version="1.10" tiledversion="1.11.2" orientation="orthogonal" renderorder="right-down" width="20" height="15" tilewidth="8" tileheight="8" infinite="0" nextlayerid="2" nextobjectid="1">
|
||||
<tileset firstgid="1" source="tileset.tsx"/>
|
||||
<layer id="1" name="Tiles" width="20" height="15">
|
||||
<data encoding="csv">
|
||||
0,0,0,0,85,85,0,0,0,0,0,0,0,0,0,0,0,0,69,69,
|
||||
0,85,85,85,85,85,0,70,70,0,85,85,85,85,85,0,0,0,0,69,
|
||||
0,85,0,0,0,85,0,0,0,0,0,0,0,0,0,70,70,85,0,0,
|
||||
0,0,0,0,0,70,70,0,0,0,0,0,0,0,0,70,70,85,85,0,
|
||||
0,0,85,0,0,70,70,1,2,2,2,2,4,0,0,0,0,0,0,0,
|
||||
0,0,85,85,0,0,0,17,0,69,69,0,20,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,17,0,0,0,0,49,2,2,4,0,0,85,85,
|
||||
70,70,70,0,0,0,0,17,0,5,0,41,0,0,53,20,0,69,69,85,
|
||||
0,70,70,0,0,0,0,17,69,0,70,0,1,2,2,36,0,0,69,0,
|
||||
0,0,85,85,0,0,0,17,0,0,0,0,17,0,0,0,0,0,0,0,
|
||||
0,0,85,85,0,0,0,33,2,2,2,2,50,0,0,0,0,85,85,0,
|
||||
0,0,85,85,85,0,0,0,0,0,0,0,0,0,0,0,0,0,85,0,
|
||||
0,69,69,0,0,0,85,85,70,70,0,0,0,0,0,0,70,0,0,0,
|
||||
0,0,69,0,0,0,0,0,0,70,70,85,85,85,0,70,70,70,0,0,
|
||||
0,85,0,85,85,85,85,0,0,0,0,85,0,0,0,70,69,69,69,0
|
||||
</data>
|
||||
</layer>
|
||||
</map>
|
||||
@ -1,139 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<tileset version="1.10" tiledversion="1.11.2" name="tileset" tilewidth="8" tileheight="8" tilecount="160" columns="16">
|
||||
<image source="../kenney-micro-roguelike/colored_tilemap_packed.png" width="128" height="80"/>
|
||||
<tile id="0">
|
||||
<properties>
|
||||
<property name="type" value="solid"/>
|
||||
</properties>
|
||||
</tile>
|
||||
<tile id="1">
|
||||
<properties>
|
||||
<property name="type" value="solid"/>
|
||||
</properties>
|
||||
</tile>
|
||||
<tile id="2">
|
||||
<properties>
|
||||
<property name="type" value="solid"/>
|
||||
</properties>
|
||||
</tile>
|
||||
<tile id="3">
|
||||
<properties>
|
||||
<property name="type" value="solid"/>
|
||||
</properties>
|
||||
</tile>
|
||||
<tile id="4">
|
||||
<properties>
|
||||
<property name="type" value="player"/>
|
||||
</properties>
|
||||
</tile>
|
||||
<tile id="16">
|
||||
<properties>
|
||||
<property name="type" value="solid"/>
|
||||
</properties>
|
||||
</tile>
|
||||
<tile id="19">
|
||||
<properties>
|
||||
<property name="type" value="solid"/>
|
||||
</properties>
|
||||
</tile>
|
||||
<tile id="32">
|
||||
<properties>
|
||||
<property name="type" value="solid"/>
|
||||
</properties>
|
||||
</tile>
|
||||
<tile id="33">
|
||||
<properties>
|
||||
<property name="type" value="solid"/>
|
||||
</properties>
|
||||
</tile>
|
||||
<tile id="34">
|
||||
<properties>
|
||||
<property name="type" value="solid"/>
|
||||
</properties>
|
||||
</tile>
|
||||
<tile id="35">
|
||||
<properties>
|
||||
<property name="type" value="solid"/>
|
||||
</properties>
|
||||
</tile>
|
||||
<tile id="39">
|
||||
<properties>
|
||||
<property name="type" value="locked_door"/>
|
||||
</properties>
|
||||
</tile>
|
||||
<tile id="40">
|
||||
<properties>
|
||||
<property name="type" value="pot"/>
|
||||
</properties>
|
||||
</tile>
|
||||
<tile id="48">
|
||||
<properties>
|
||||
<property name="type" value="solid"/>
|
||||
</properties>
|
||||
</tile>
|
||||
<tile id="49">
|
||||
<properties>
|
||||
<property name="type" value="solid"/>
|
||||
</properties>
|
||||
</tile>
|
||||
<tile id="50">
|
||||
<properties>
|
||||
<property name="type" value="solid"/>
|
||||
</properties>
|
||||
</tile>
|
||||
<tile id="51">
|
||||
<properties>
|
||||
<property name="type" value="solid"/>
|
||||
</properties>
|
||||
</tile>
|
||||
<tile id="52">
|
||||
<properties>
|
||||
<property name="type" value="staircase"/>
|
||||
</properties>
|
||||
</tile>
|
||||
<tile id="64">
|
||||
<properties>
|
||||
<property name="type" value="solid"/>
|
||||
</properties>
|
||||
</tile>
|
||||
<tile id="65">
|
||||
<properties>
|
||||
<property name="type" value="solid"/>
|
||||
</properties>
|
||||
</tile>
|
||||
<tile id="66">
|
||||
<properties>
|
||||
<property name="type" value="solid"/>
|
||||
</properties>
|
||||
</tile>
|
||||
<tile id="67">
|
||||
<properties>
|
||||
<property name="type" value="solid"/>
|
||||
</properties>
|
||||
</tile>
|
||||
<tile id="80">
|
||||
<properties>
|
||||
<property name="type" value="solid"/>
|
||||
</properties>
|
||||
</tile>
|
||||
<tile id="81">
|
||||
<properties>
|
||||
<property name="type" value="solid"/>
|
||||
</properties>
|
||||
</tile>
|
||||
<tile id="82">
|
||||
<properties>
|
||||
<property name="type" value="solid"/>
|
||||
</properties>
|
||||
</tile>
|
||||
<tile id="83">
|
||||
<properties>
|
||||
<property name="type" value="solid"/>
|
||||
</properties>
|
||||
</tile>
|
||||
<tile id="90">
|
||||
<properties>
|
||||
<property name="type" value="key"/>
|
||||
</properties>
|
||||
</tile>
|
||||
</tileset>
|
||||
@ -1,31 +0,0 @@
|
||||
const GenerationalArrayList = @import("./generational_array_list.zig").GenerationalArrayList;
|
||||
|
||||
const Gfx = @import("./graphics.zig");
|
||||
|
||||
const Math = @import("./math.zig");
|
||||
const Vec2 = Math.Vec2;
|
||||
|
||||
const Entity = @This();
|
||||
|
||||
pub const List = GenerationalArrayList(Entity);
|
||||
pub const Id = List.Id;
|
||||
|
||||
pub const Type = enum {
|
||||
nil,
|
||||
player,
|
||||
solid,
|
||||
pot,
|
||||
staircase,
|
||||
door,
|
||||
key,
|
||||
};
|
||||
|
||||
type: Type,
|
||||
position: Vec2,
|
||||
|
||||
locked: bool = false,
|
||||
|
||||
render_tile: ?union(enum) {
|
||||
position: Vec2,
|
||||
id: Gfx.TileId
|
||||
} = null
|
||||
556
src/game.zig
556
src/game.zig
@ -1,556 +0,0 @@
|
||||
const std = @import("std");
|
||||
const Allocator = std.mem.Allocator;
|
||||
const assert = std.debug.assert;
|
||||
|
||||
const Math = @import("./math.zig");
|
||||
const Vec2 = Math.Vec2;
|
||||
const Vec4 = Math.Vec4;
|
||||
const rgb = Math.rgb;
|
||||
const rgb_hex = Math.rgb_hex;
|
||||
|
||||
const Timer = @import("./timer.zig");
|
||||
const Window = @import("./window.zig");
|
||||
const imgui = @import("./imgui.zig");
|
||||
const Gfx = @import("./graphics.zig");
|
||||
const Entity = @import("./entity.zig");
|
||||
|
||||
const tiled = @import("./tiled.zig");
|
||||
|
||||
const Game = @This();
|
||||
|
||||
pub const Input = struct {
|
||||
dt: f64,
|
||||
move_up: Window.KeyState,
|
||||
move_down: Window.KeyState,
|
||||
move_left: Window.KeyState,
|
||||
move_right: Window.KeyState,
|
||||
restart: bool
|
||||
};
|
||||
|
||||
pub const Level = struct {
|
||||
entities: Entity.List,
|
||||
timers: Timer.List,
|
||||
|
||||
pub const empty = Level{
|
||||
.entities = .empty,
|
||||
.timers = .empty
|
||||
};
|
||||
|
||||
pub fn clone(self: *Level, gpa: Allocator) !Level {
|
||||
var entities = try self.entities.clone(gpa);
|
||||
errdefer entities.deinit(gpa);
|
||||
|
||||
var timers = try self.timers.clone(gpa);
|
||||
errdefer timers.deinit(gpa);
|
||||
|
||||
return Level{
|
||||
.entities = entities,
|
||||
.timers = timers
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Level, gpa: Allocator) void {
|
||||
self.entities.deinit(gpa);
|
||||
self.timers.deinit(gpa);
|
||||
}
|
||||
};
|
||||
|
||||
const key = "XXXXX-XXXXX-XXXXX";
|
||||
|
||||
gpa: Allocator,
|
||||
|
||||
canvas_size: Vec2,
|
||||
level: Level,
|
||||
|
||||
current_level: u32,
|
||||
levels: std.ArrayList(Level),
|
||||
|
||||
last_up_repeat_at: ?f64 = null,
|
||||
last_down_repeat_at: ?f64 = null,
|
||||
last_left_repeat_at: ?f64 = null,
|
||||
last_right_repeat_at: ?f64 = null,
|
||||
|
||||
show_grid: bool = false,
|
||||
|
||||
timers: Timer.List = .empty,
|
||||
level_exit_transition: ?Timer.Id = null,
|
||||
level_enter_transition: ?Timer.Id = null,
|
||||
|
||||
finale: bool = false,
|
||||
finale_timer: ?Timer.Id = null,
|
||||
finale_counter: u32 = 0,
|
||||
|
||||
pub fn init(gpa: Allocator) !Game {
|
||||
var self = Game{
|
||||
.gpa = gpa,
|
||||
.canvas_size = (Vec2.init(20, 15)),
|
||||
.level = .empty,
|
||||
.levels = .empty,
|
||||
.current_level = 0,
|
||||
};
|
||||
errdefer self.deinit();
|
||||
|
||||
const manager = try tiled.ResourceManager.init();
|
||||
defer manager.deinit();
|
||||
|
||||
try manager.loadTilesetFromBuffer(@embedFile("assets/tiled/tileset.tsx"), "tileset.tsx");
|
||||
|
||||
try self.levels.append(gpa, try loadLevelFromEmbedFile(gpa, manager, "assets/tiled/first.tmx"));
|
||||
try self.levels.append(gpa, try loadLevelFromEmbedFile(gpa, manager, "assets/tiled/second.tmx"));
|
||||
try self.levels.append(gpa, try loadLevelFromEmbedFile(gpa, manager, "assets/tiled/third.tmx"));
|
||||
try self.levels.append(gpa, try loadLevelFromEmbedFile(gpa, manager, "assets/tiled/fourth.tmx"));
|
||||
|
||||
try self.restartLevel();
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
fn restartLevel(self: *Game) !void {
|
||||
const level_copy = try self.levels.items[self.current_level].clone(self.gpa);
|
||||
errdefer level_copy.deinit(self.gpa);
|
||||
|
||||
self.level.deinit(self.gpa);
|
||||
self.level = level_copy;
|
||||
}
|
||||
|
||||
fn nextLevel(self: *Game) !void {
|
||||
if (self.level_exit_transition != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (self.current_level < self.levels.items.len) {
|
||||
self.level_exit_transition = try self.timers.start(self.gpa, .{
|
||||
.duration = 1
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn loadLevelFromEmbedFile(gpa: Allocator, manager: tiled.ResourceManager, comptime path: []const u8) !Level {
|
||||
const map = try manager.loadMapFromBuffer(@embedFile(path));
|
||||
defer map.deinit();
|
||||
|
||||
return try loadLevelFromTiled(gpa, map);
|
||||
}
|
||||
|
||||
fn loadLevelFromTiled(gpa: Allocator, map: tiled.Map) !Level {
|
||||
var level: Level = .empty;
|
||||
errdefer level.deinit(gpa);
|
||||
|
||||
var layer_iter = map.iterLayers();
|
||||
while (layer_iter.next()) |layer| {
|
||||
if (layer.layer.visible == 0) {
|
||||
continue;
|
||||
}
|
||||
if (layer.layer.type != @intFromEnum(tiled.Layer.Type.layer)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const map_width = map.map.width;
|
||||
for (0..map.map.height) |y| {
|
||||
for (0..map_width) |x| {
|
||||
|
||||
const tile = map.getTile(layer, x, y) orelse continue;
|
||||
const tile_props = tiled.Properties{ .inner = tile.tile.properties };
|
||||
const tile_type: []const u8 = std.mem.span(tile_props.getPropertyString("type") orelse "");
|
||||
|
||||
const tile_size = Vec2.init(8, 8);
|
||||
var entity: Entity = .{
|
||||
.type = .nil,
|
||||
.position = Vec2.init(@floatFromInt(x), @floatFromInt(y)),
|
||||
.render_tile = .{ .position = tile.getUpperLeft().divide(tile_size) },
|
||||
};
|
||||
if (std.mem.eql(u8, tile_type, "player")) {
|
||||
entity.type = .player;
|
||||
} else if (std.mem.eql(u8, tile_type, "key")) {
|
||||
entity.type = .key;
|
||||
} else if (std.mem.eql(u8, tile_type, "locked_door")) {
|
||||
entity.type = .door;
|
||||
entity.locked = true;
|
||||
} else if (std.mem.eql(u8, tile_type, "pot")) {
|
||||
entity.type = .pot;
|
||||
} else if (std.mem.eql(u8, tile_type, "staircase")) {
|
||||
entity.type = .staircase;
|
||||
} else if (std.mem.eql(u8, tile_type, "solid")) {
|
||||
entity.type = .solid;
|
||||
}
|
||||
|
||||
_ = try level.entities.insert(gpa, entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return level;
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Game) void {
|
||||
self.timers.deinit(self.gpa);
|
||||
self.level.deinit(self.gpa);
|
||||
for (self.levels.items) |*level| {
|
||||
level.deinit(self.gpa);
|
||||
}
|
||||
self.levels.deinit(self.gpa);
|
||||
}
|
||||
|
||||
fn drawGrid(self: *Game, size: Vec2, color: Vec4, line_width: f32) void {
|
||||
var x: f32 = 0;
|
||||
while (x < self.canvas_size.x) {
|
||||
x += size.x;
|
||||
Gfx.drawLine(
|
||||
.init(x, 0),
|
||||
.init(x, self.canvas_size.y),
|
||||
color,
|
||||
line_width
|
||||
);
|
||||
}
|
||||
|
||||
var y: f32 = 0;
|
||||
while (y < self.canvas_size.y) {
|
||||
y += size.y;
|
||||
Gfx.drawLine(
|
||||
.init(0, y),
|
||||
.init(self.canvas_size.x, y),
|
||||
color,
|
||||
line_width
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn getEntityAt(self: *Game, pos: Vec2) ?Entity.Id {
|
||||
var iter = self.level.entities.iterator();
|
||||
while (iter.next()) |tuple| {
|
||||
const entity = tuple.item;
|
||||
if (entity.type == .nil) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (entity.position.eql(pos)) {
|
||||
return tuple.id;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
fn isSolidAt(self: *Game, pos: Vec2) bool {
|
||||
if (self.getEntityAt(pos)) |entity_id| {
|
||||
const entity = self.level.entities.getAssumeExists(entity_id);
|
||||
return entity.type == .solid;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
fn canMove(self: *Game, entity: *Entity, dir: Vec2) bool {
|
||||
const next_pos = entity.position.add(dir);
|
||||
if (self.isSolidAt(next_pos)) {
|
||||
return false;
|
||||
}
|
||||
if (next_pos.x < 0 or next_pos.x >= self.canvas_size.x) {
|
||||
return false;
|
||||
}
|
||||
if (next_pos.y < 0 or next_pos.y >= self.canvas_size.y) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
fn moveEntity(self: *Game, entity_id: Entity.Id, dir: Vec2) bool {
|
||||
const entity = self.level.entities.get(entity_id) orelse return true;
|
||||
|
||||
if (entity.type == .solid) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (entity.type == .nil) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (dir.x == 0 and dir.y == 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const next_pos = entity.position.add(dir);
|
||||
if (self.getEntityAt(next_pos)) |next_entity_id| {
|
||||
const next_entity = self.level.entities.getAssumeExists(next_entity_id);
|
||||
if (next_entity.type == .door and next_entity.locked and entity.type == .key) {
|
||||
_ = self.level.entities.removeAssumeExists(entity_id);
|
||||
next_entity.locked = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (next_entity.type == .pot or next_entity.type == .key) {
|
||||
if (!self.moveEntity(next_entity_id, dir)) {
|
||||
return false;
|
||||
}
|
||||
} else if (next_entity.type == .door) {
|
||||
if (next_entity.locked) {
|
||||
return false;
|
||||
}
|
||||
} else if (next_entity.type == .solid) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
entity.position = next_pos;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
pub fn getInput(self: *Game, window: *Window) Input {
|
||||
_ = self; // autofix
|
||||
const dt = @as(f32, @floatFromInt(window.frame_dt_ns)) / std.time.ns_per_s;
|
||||
|
||||
return Input{
|
||||
.dt = dt,
|
||||
.move_up = window.getKeyState(.W),
|
||||
.move_down = window.getKeyState(.S),
|
||||
.move_left = window.getKeyState(.A),
|
||||
.move_right = window.getKeyState(.D),
|
||||
.restart = window.isKeyPressed(.R)
|
||||
};
|
||||
}
|
||||
|
||||
fn drawEntity(self: *Game, entity: *Entity) void {
|
||||
_ = self; // autofix
|
||||
if (entity.render_tile) |render_tile| {
|
||||
var tile_coord = switch (render_tile) {
|
||||
.id => |tile_id| Gfx.getTileCoords(tile_id),
|
||||
.position => |position| position
|
||||
};
|
||||
|
||||
if (entity.type == .door) {
|
||||
if (entity.locked) {
|
||||
tile_coord = Gfx.getTileCoords(.locked_door);
|
||||
} else {
|
||||
tile_coord = Gfx.getTileCoords(.open_door);
|
||||
}
|
||||
}
|
||||
|
||||
Gfx.drawTile(tile_coord, entity.position, .init(1,1), rgb(255, 255, 255));
|
||||
}
|
||||
}
|
||||
|
||||
fn hasStaricaseAt(self: *Game, position: Vec2) bool {
|
||||
var iter = self.level.entities.iterator();
|
||||
while (iter.nextItem()) |entity| {
|
||||
if (entity.type == .staircase and entity.position.eql(position)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
pub fn tickLevel(self: *Game, input: Input) !void {
|
||||
const bg_color = rgb_hex("#222323").?;
|
||||
|
||||
if (input.restart) {
|
||||
try self.restartLevel();
|
||||
}
|
||||
|
||||
self.level.timers.now += input.dt;
|
||||
|
||||
var cover_opacity: f32 = 0;
|
||||
var can_move = true;
|
||||
|
||||
if (self.level_exit_transition) |timer| {
|
||||
can_move = false;
|
||||
|
||||
cover_opacity = self.timers.percent_passed(timer);
|
||||
|
||||
if (self.timers.finished(timer)) {
|
||||
self.current_level += 1;
|
||||
if (self.current_level == self.levels.items.len) {
|
||||
self.finale = true;
|
||||
return;
|
||||
} else {
|
||||
try self.restartLevel();
|
||||
self.level_exit_transition = null;
|
||||
|
||||
self.level_enter_transition = try self.timers.start(self.gpa, .{
|
||||
.duration = 1
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (self.level_enter_transition) |timer| {
|
||||
cover_opacity = 1 - self.timers.percent_passed(timer);
|
||||
if (self.timers.finished(timer)) {
|
||||
self.level_enter_transition = null;
|
||||
}
|
||||
}
|
||||
|
||||
var move: Vec2 = .init(0, 0);
|
||||
if (can_move) {
|
||||
const repeat_options = Window.KeyState.RepeatOptions{
|
||||
.first_at = 0.3,
|
||||
.period = 0.1
|
||||
};
|
||||
|
||||
if (input.move_up.pressed or input.move_up.repeat(&self.last_up_repeat_at, repeat_options)) {
|
||||
move.y -= 1;
|
||||
}
|
||||
if (input.move_down.pressed or input.move_down.repeat(&self.last_down_repeat_at, repeat_options)) {
|
||||
move.y += 1;
|
||||
}
|
||||
if (input.move_left.pressed or input.move_left.repeat(&self.last_left_repeat_at, repeat_options)) {
|
||||
move.x -= 1;
|
||||
}
|
||||
if (input.move_right.pressed or input.move_right.repeat(&self.last_right_repeat_at, repeat_options)) {
|
||||
move.x += 1;
|
||||
}
|
||||
}
|
||||
|
||||
var iter = self.level.entities.iterator();
|
||||
while (iter.next()) |tuple| {
|
||||
const entity = tuple.item;
|
||||
const entity_id = tuple.id;
|
||||
if (entity.type == .player) {
|
||||
_ = self.moveEntity(entity_id, move);
|
||||
if (self.hasStaricaseAt(entity.position)) {
|
||||
try self.nextLevel();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var top_layer: std.ArrayList(Entity.Id) = .empty;
|
||||
defer top_layer.deinit(self.gpa);
|
||||
|
||||
var bottom_layer: std.ArrayList(Entity.Id) = .empty;
|
||||
defer bottom_layer.deinit(self.gpa);
|
||||
|
||||
iter = self.level.entities.iterator();
|
||||
while (iter.next()) |tuple| {
|
||||
const entity = tuple.item;
|
||||
const entity_id = tuple.id;
|
||||
if (entity.type == .player or entity.type == .key or entity.type == .pot) {
|
||||
try top_layer.append(self.gpa, entity_id);
|
||||
} else {
|
||||
try bottom_layer.append(self.gpa, entity_id);
|
||||
}
|
||||
}
|
||||
|
||||
for (bottom_layer.items) |entity_id| {
|
||||
const entity = self.level.entities.getAssumeExists(entity_id);
|
||||
self.drawEntity(entity);
|
||||
}
|
||||
|
||||
for (top_layer.items) |entity_id| {
|
||||
const entity = self.level.entities.getAssumeExists(entity_id);
|
||||
self.drawEntity(entity);
|
||||
}
|
||||
|
||||
if (cover_opacity != 0) {
|
||||
Gfx.drawRectangle(.init(0, 0), .init(self.canvas_size.x, self.canvas_size.y), bg_color.multiply(Vec4.init(1, 1, 1, cover_opacity)));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn tickFinale(self: *Game) !void {
|
||||
const color = rgb(200, 200, 200);
|
||||
|
||||
const Line = struct {
|
||||
pos: Vec2,
|
||||
font: Gfx.Font,
|
||||
text: []const u8,
|
||||
};
|
||||
|
||||
const lines = [5]Line{
|
||||
.{
|
||||
.pos = Vec2.init(1, 1),
|
||||
.font = Gfx.Font.default,
|
||||
.text = "Congratulations scientist"
|
||||
},
|
||||
.{
|
||||
.pos = Vec2.init(1, 2),
|
||||
.font = Gfx.Font.default,
|
||||
.text = "You have passed the entrance exam"
|
||||
},
|
||||
.{
|
||||
.pos = Vec2.init(1, 3),
|
||||
.font = Gfx.Font.default,
|
||||
.text = "Here is your entry code"
|
||||
},
|
||||
.{
|
||||
.pos = Vec2.init(1, 5),
|
||||
.font = Gfx.Font.bold,
|
||||
.text = key
|
||||
},
|
||||
.{
|
||||
.pos = Vec2.init(1, 7),
|
||||
.font = Gfx.Font.default,
|
||||
.text = "I'll meet you at the lab"
|
||||
}
|
||||
};
|
||||
|
||||
if (self.finale_timer == null) {
|
||||
if (self.finale_counter < lines.len) {
|
||||
self.finale_timer = try self.timers.start(self.gpa, .{
|
||||
.duration = 2,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (self.finale_timer) |timer| {
|
||||
if (self.timers.finished(timer)) {
|
||||
self.finale_timer = null;
|
||||
self.finale_counter += 1;
|
||||
}
|
||||
}
|
||||
|
||||
for (0..self.finale_counter) |i| {
|
||||
const line = lines[i];
|
||||
try Gfx.drawText(self.gpa, line.pos, line.font, color, line.text);
|
||||
}
|
||||
|
||||
if (self.finale_counter < lines.len) {
|
||||
var opacity: f32 = 0;
|
||||
if (self.finale_timer) |timer| {
|
||||
opacity = self.timers.percent_passed(timer);
|
||||
}
|
||||
|
||||
const line = lines[self.finale_counter];
|
||||
try Gfx.drawText(self.gpa, line.pos, line.font, color.multiply(Vec4.init(1, 1, 1, opacity)), line.text);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn tick(self: *Game, input: Input) !void {
|
||||
const bg_color = rgb_hex("#222323").?;
|
||||
|
||||
Gfx.drawRectangle(.init(0, 0), .init(self.canvas_size.x, self.canvas_size.y), bg_color);
|
||||
if (self.show_grid) {
|
||||
self.drawGrid(.init(1, 1), rgb(20, 20, 20), 0.1);
|
||||
}
|
||||
|
||||
self.timers.now += input.dt;
|
||||
|
||||
if (self.finale) {
|
||||
try self.tickFinale();
|
||||
} else {
|
||||
try self.tickLevel(input);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn debug(self: *Game) !void {
|
||||
if (!imgui.beginWindow(.{
|
||||
.name = "Debug",
|
||||
.pos = Vec2.init(20, 20),
|
||||
.size = Vec2.init(400, 200),
|
||||
})) {
|
||||
return;
|
||||
}
|
||||
defer imgui.endWindow();
|
||||
|
||||
imgui.textFmt("Entities: {}", .{self.level.entities.len});
|
||||
imgui.textFmt("Timers: {}", .{self.level.timers.array_list.len});
|
||||
_ = imgui.checkbox("Show grid", &self.show_grid);
|
||||
|
||||
if (imgui.button("Skip level")) {
|
||||
try self.nextLevel();
|
||||
}
|
||||
|
||||
if (imgui.button("Finale")) {
|
||||
self.finale = true;
|
||||
}
|
||||
}
|
||||
@ -1,480 +0,0 @@
|
||||
const std = @import("std");
|
||||
const tracy = @import("tracy");
|
||||
const builtin = @import("builtin");
|
||||
const assert = std.debug.assert;
|
||||
const Allocator = std.mem.Allocator;
|
||||
|
||||
const Index = u24;
|
||||
const Generation = u8;
|
||||
|
||||
pub fn GenerationalArrayList(Item: type) type {
|
||||
assert(@bitSizeOf(Generation) % 8 == 0);
|
||||
assert(@bitSizeOf(Index) % 8 == 0);
|
||||
|
||||
return struct {
|
||||
const Self = @This();
|
||||
|
||||
items: [*]Item,
|
||||
generations: [*]Generation,
|
||||
unused: [*]u8,
|
||||
|
||||
len: u32,
|
||||
capacity: u32,
|
||||
|
||||
count: u32,
|
||||
|
||||
pub const empty = Self{
|
||||
.items = &[_]Item{},
|
||||
.generations = &[_]Generation{},
|
||||
.unused = &[_]u8{},
|
||||
.capacity = 0,
|
||||
.len = 0,
|
||||
.count = 0
|
||||
};
|
||||
|
||||
pub const Id = packed struct {
|
||||
generation: Generation,
|
||||
index: Index,
|
||||
|
||||
// TODO: Maybe `Id.Optional` type should be created to ensure .wrap() and .toOptional()
|
||||
pub const none = Id{
|
||||
.generation = std.math.maxInt(Generation),
|
||||
.index = std.math.maxInt(Index),
|
||||
};
|
||||
|
||||
pub fn format(self: Id, writer: *std.Io.Writer) std.Io.Writer.Error!void {
|
||||
if (self == Id.none) {
|
||||
try writer.print("Id({s}){{ .none }}", .{ @typeName(Item) });
|
||||
} else {
|
||||
try writer.print("Id({s}){{ {}, {} }}", .{ @typeName(Item), self.index, self.generation });
|
||||
}
|
||||
}
|
||||
|
||||
pub fn asInt(self: Id) u32 {
|
||||
return @bitCast(self);
|
||||
}
|
||||
};
|
||||
|
||||
pub const ItemWithId = struct {
|
||||
id: Id,
|
||||
item: *Item,
|
||||
};
|
||||
|
||||
pub const Iterator = struct {
|
||||
array_list: *Self,
|
||||
index: Index,
|
||||
|
||||
pub fn nextId(self: *Iterator) ?Id {
|
||||
while (self.index < self.array_list.len) {
|
||||
const index = self.index;
|
||||
self.index += 1;
|
||||
|
||||
// TODO: Inline the `byte_index` calculate for better speed.
|
||||
// Probably not needed. Idk
|
||||
if (self.array_list.isUnused(index)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
return Id{
|
||||
.index = @intCast(index),
|
||||
.generation = self.array_list.generations[index]
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
pub fn nextItem(self: *Iterator) ?*Item {
|
||||
if (self.nextId()) |id| {
|
||||
return &self.array_list.items[id.index];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
pub fn next(self: *Iterator) ?ItemWithId {
|
||||
if (self.nextId()) |id| {
|
||||
return ItemWithId{
|
||||
.id = id,
|
||||
.item = &self.array_list.items[id.index]
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
pub const Metadata = extern struct {
|
||||
len: u32,
|
||||
count: u32
|
||||
};
|
||||
|
||||
fn divCeilGeneration(num: u32) u32 {
|
||||
return std.math.divCeil(u32, num, @bitSizeOf(Generation)) catch unreachable;
|
||||
}
|
||||
|
||||
fn divFloorGeneration(num: u32) u32 {
|
||||
return @divFloor(num, @bitSizeOf(Generation));
|
||||
}
|
||||
|
||||
pub fn ensureTotalCapacityPrecise(self: *Self, allocator: Allocator, new_capacity: u32) !void {
|
||||
if (new_capacity > std.math.maxInt(Index)) {
|
||||
return error.OutOfIndexSpace;
|
||||
}
|
||||
|
||||
// TODO: Shrinking is not supported
|
||||
assert(new_capacity >= self.capacity);
|
||||
|
||||
const unused_bit_array_len = divCeilGeneration(self.capacity);
|
||||
const new_unused_bit_array_len = divCeilGeneration(new_capacity);
|
||||
|
||||
// TODO: Handle allocation failure case
|
||||
const new_unused = try allocator.realloc(self.unused[0..unused_bit_array_len], new_unused_bit_array_len);
|
||||
const new_items = try allocator.realloc(self.items[0..self.capacity], new_capacity);
|
||||
const new_generations = try allocator.realloc(self.generations[0..self.capacity], new_capacity);
|
||||
|
||||
self.unused = new_unused.ptr;
|
||||
self.items = new_items.ptr;
|
||||
self.generations = new_generations.ptr;
|
||||
self.capacity = new_capacity;
|
||||
}
|
||||
|
||||
fn growCapacity(current: u32, minimum: u32) u32 {
|
||||
const init_capacity = @as(comptime_int, @max(1, std.atomic.cache_line / @sizeOf(Item)));
|
||||
|
||||
var new = current;
|
||||
while (true) {
|
||||
new +|= new / 2 + init_capacity;
|
||||
if (new >= minimum) {
|
||||
return new;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn ensureTotalCapacity(self: *Self, allocator: Allocator, new_capacity: u32) !void {
|
||||
if (self.capacity >= new_capacity) return;
|
||||
|
||||
const better_capacity = Self.growCapacity(self.capacity, new_capacity);
|
||||
try self.ensureTotalCapacityPrecise(allocator, better_capacity);
|
||||
}
|
||||
|
||||
pub fn clearRetainingCapacity(self: *Self) void {
|
||||
self.count = 0;
|
||||
self.len = 0;
|
||||
}
|
||||
|
||||
pub fn ensureUnusedCapacity(self: *Self, allocator: Allocator, unused_capacity: u32) !void {
|
||||
try self.ensureTotalCapacity(allocator, self.len + unused_capacity);
|
||||
}
|
||||
|
||||
fn findFirstUnused(self: *Self) ?Index {
|
||||
for (0..divCeilGeneration(self.len)) |byte_index| {
|
||||
if (self.unused[byte_index] != 0) {
|
||||
const found = @ctz(self.unused[byte_index]) + byte_index * @bitSizeOf(Generation);
|
||||
if (found < self.len) {
|
||||
return @intCast(found);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
fn markUnused(self: *Self, index: Index, unused: bool) void {
|
||||
assert(index < self.len);
|
||||
|
||||
const byte_index = divFloorGeneration(index);
|
||||
const bit_index = @mod(index, @bitSizeOf(Generation));
|
||||
const bit_flag = @as(u8, 1) << @intCast(bit_index);
|
||||
if (unused) {
|
||||
self.unused[byte_index] |= bit_flag;
|
||||
} else {
|
||||
self.unused[byte_index] &= ~bit_flag;
|
||||
}
|
||||
}
|
||||
|
||||
fn isUnused(self: *Self, index: Index) bool {
|
||||
assert(index < self.len);
|
||||
|
||||
const byte_index = divFloorGeneration(index);
|
||||
const bit_index = @mod(index, @bitSizeOf(Generation));
|
||||
const bit_flag = @as(u8, 1) << @intCast(bit_index);
|
||||
return (self.unused[byte_index] & bit_flag) != 0;
|
||||
}
|
||||
|
||||
pub fn insertUndefined(self: *Self, allocator: Allocator) !Id {
|
||||
var unused_index: Index = undefined;
|
||||
|
||||
if (self.findFirstUnused()) |index| {
|
||||
unused_index = index;
|
||||
} else {
|
||||
try self.ensureUnusedCapacity(allocator, 1);
|
||||
|
||||
unused_index = @intCast(self.len);
|
||||
self.len += 1;
|
||||
self.generations[unused_index] = 0;
|
||||
}
|
||||
|
||||
self.markUnused(unused_index, false);
|
||||
self.count += 1;
|
||||
|
||||
const id = Id{
|
||||
.index = @intCast(unused_index),
|
||||
.generation = self.generations[unused_index]
|
||||
};
|
||||
|
||||
assert(id != Id.none);
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
pub fn insert(self: *Self, allocator: Allocator, item: Item) !Id {
|
||||
const id = try self.insertUndefined(allocator);
|
||||
|
||||
const new_item_ptr = self.getAssumeExists(id);
|
||||
new_item_ptr.* = item;
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
pub fn exists(self: *Self, id: Id) bool {
|
||||
if (id.index >= self.len) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (self.isUnused(id.index)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (self.generations[id.index] != id.generation) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
pub fn removeAssumeExists(self: *Self, id: Id) void {
|
||||
assert(self.exists(id));
|
||||
|
||||
self.markUnused(id.index, true);
|
||||
// TODO: Maybe a log should be shown when a wrap-around occurs?
|
||||
self.generations[id.index] +%= 1;
|
||||
self.count -= 1;
|
||||
}
|
||||
|
||||
pub fn remove(self: *Self, id: Id) bool {
|
||||
if (!self.exists(id)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
self.removeAssumeExists(id);
|
||||
return true;
|
||||
}
|
||||
|
||||
pub fn getAssumeExists(self: *Self, id: Id) *Item {
|
||||
assert(self.exists(id));
|
||||
|
||||
return &self.items[id.index];
|
||||
}
|
||||
|
||||
pub fn get(self: *Self, id: Id) ?*Item {
|
||||
if (self.exists(id)) {
|
||||
return self.getAssumeExists(id);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn iterator(self: *Self) Iterator {
|
||||
return Iterator{
|
||||
.array_list = self,
|
||||
.index = 0
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Self, allocator: Allocator) void {
|
||||
allocator.free(self.unused[0..divCeilGeneration(self.capacity)]);
|
||||
allocator.free(self.generations[0..self.capacity]);
|
||||
allocator.free(self.items[0..self.capacity]);
|
||||
}
|
||||
|
||||
pub fn clone(self: *Self, allocator: Allocator) !Self {
|
||||
const items = try allocator.dupe(Item, self.items[0..self.capacity]);
|
||||
errdefer allocator.free(items);
|
||||
|
||||
const generations = try allocator.dupe(Generation, self.generations[0..self.capacity]);
|
||||
errdefer allocator.free(generations);
|
||||
|
||||
const unused = try allocator.dupe(u8, self.unused[0..divCeilGeneration(self.capacity)]);
|
||||
errdefer allocator.free(unused);
|
||||
|
||||
return Self{
|
||||
.items = items.ptr,
|
||||
.generations = generations.ptr,
|
||||
.unused = unused.ptr,
|
||||
.len = self.len,
|
||||
.count = self.count,
|
||||
.capacity = self.capacity
|
||||
};
|
||||
}
|
||||
|
||||
pub fn getMetadata(self: *Self) Metadata {
|
||||
return Metadata{
|
||||
.len = self.len,
|
||||
.count = self.count
|
||||
};
|
||||
}
|
||||
|
||||
pub fn write(self: *Self, writer: *std.Io.Writer, endian: std.builtin.Endian) !void {
|
||||
const zone = tracy.beginZone(@src(), .{ .name = "gen array list write" });
|
||||
defer zone.end();
|
||||
|
||||
try writer.writeSliceEndian(Item, self.items[0..self.len], endian);
|
||||
try writer.writeSliceEndian(Generation, self.generations[0..self.len], endian);
|
||||
try writer.writeAll(self.unused[0..divCeilGeneration(self.len)]);
|
||||
}
|
||||
|
||||
pub fn read(
|
||||
self: *Self,
|
||||
allocator: Allocator,
|
||||
reader: *std.Io.Reader,
|
||||
endian: std.builtin.Endian,
|
||||
metadata: Metadata
|
||||
) !void {
|
||||
const zone = tracy.beginZone(@src(), .{ .name = "gen array list read" });
|
||||
defer zone.end();
|
||||
|
||||
try self.ensureTotalCapacity(allocator, metadata.len);
|
||||
|
||||
try reader.readSliceEndian(Item, self.items[0..metadata.len], endian);
|
||||
try reader.readSliceEndian(Generation, self.generations[0..metadata.len], endian);
|
||||
try reader.readSliceAll(self.unused[0..divCeilGeneration(metadata.len)]);
|
||||
|
||||
self.len = metadata.len;
|
||||
self.count = metadata.count;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const TestArray = GenerationalArrayList(u32);
|
||||
|
||||
test "insert & remove" {
|
||||
const expect = std.testing.expect;
|
||||
const gpa = std.testing.allocator;
|
||||
|
||||
var array_list: TestArray = .empty;
|
||||
defer array_list.deinit(gpa);
|
||||
|
||||
const id1 = try array_list.insert(gpa, 10);
|
||||
try expect(array_list.exists(id1));
|
||||
try expect(array_list.remove(id1));
|
||||
try expect(!array_list.exists(id1));
|
||||
try expect(!array_list.remove(id1));
|
||||
|
||||
const id2 = try array_list.insert(gpa, 10);
|
||||
try expect(array_list.exists(id2));
|
||||
try expect(!array_list.exists(id1));
|
||||
try expect(id1.index == id2.index);
|
||||
}
|
||||
|
||||
test "generation wrap around" {
|
||||
const expectEqual = std.testing.expectEqual;
|
||||
const gpa = std.testing.allocator;
|
||||
|
||||
var array_list: TestArray = .empty;
|
||||
defer array_list.deinit(gpa);
|
||||
|
||||
// Grow array list so that at least 1 slot exists
|
||||
const id1 = try array_list.insert(gpa, 10);
|
||||
array_list.removeAssumeExists(id1);
|
||||
|
||||
// Artificially increase generation count
|
||||
array_list.generations[id1.index] = std.math.maxInt(Generation);
|
||||
|
||||
// Check if generation wraps around
|
||||
const id2 = try array_list.insert(gpa, 10);
|
||||
array_list.removeAssumeExists(id2);
|
||||
try expectEqual(id1.index, id2.index);
|
||||
try expectEqual(0, array_list.generations[id1.index]);
|
||||
}
|
||||
|
||||
test "iterator" {
|
||||
const expectEqual = std.testing.expectEqual;
|
||||
const gpa = std.testing.allocator;
|
||||
|
||||
var array_list: TestArray = .empty;
|
||||
defer array_list.deinit(gpa);
|
||||
|
||||
// Create array which has a hole
|
||||
const id1 = try array_list.insert(gpa, 1);
|
||||
const id2 = try array_list.insert(gpa, 2);
|
||||
const id3 = try array_list.insert(gpa, 3);
|
||||
|
||||
array_list.removeAssumeExists(id2);
|
||||
|
||||
var iter = array_list.iterator();
|
||||
try expectEqual(
|
||||
TestArray.ItemWithId{
|
||||
.id = id1,
|
||||
.item = array_list.getAssumeExists(id1)
|
||||
},
|
||||
iter.next().?
|
||||
);
|
||||
try expectEqual(
|
||||
TestArray.ItemWithId{
|
||||
.id = id3,
|
||||
.item = array_list.getAssumeExists(id3)
|
||||
},
|
||||
iter.next().?
|
||||
);
|
||||
try expectEqual(null, iter.next());
|
||||
}
|
||||
|
||||
test "read & write" {
|
||||
const expectEqual = std.testing.expectEqual;
|
||||
const gpa = std.testing.allocator;
|
||||
|
||||
var array_list1: TestArray = .empty;
|
||||
defer array_list1.deinit(gpa);
|
||||
|
||||
var array_list2: TestArray = .empty;
|
||||
defer array_list2.deinit(gpa);
|
||||
|
||||
const id1 = try array_list1.insert(gpa, 1);
|
||||
const id2 = try array_list1.insert(gpa, 2);
|
||||
const id3 = try array_list1.insert(gpa, 3);
|
||||
|
||||
var buffer: [1024]u8 = undefined;
|
||||
var writer = std.Io.Writer.fixed(&buffer);
|
||||
const native_endian = builtin.cpu.arch.endian();
|
||||
|
||||
try array_list1.write(&writer, native_endian);
|
||||
|
||||
var reader = std.Io.Reader.fixed(writer.buffered());
|
||||
try array_list2.read(gpa, &reader, native_endian, array_list1.getMetadata());
|
||||
|
||||
try expectEqual(array_list1.getAssumeExists(id1).*, array_list2.getAssumeExists(id1).*);
|
||||
try expectEqual(array_list1.getAssumeExists(id2).*, array_list2.getAssumeExists(id2).*);
|
||||
try expectEqual(array_list1.getAssumeExists(id3).*, array_list2.getAssumeExists(id3).*);
|
||||
try expectEqual(array_list1.count, array_list2.count);
|
||||
}
|
||||
|
||||
test "clear retaining capacity" {
|
||||
const expect = std.testing.expect;
|
||||
const expectEqual = std.testing.expectEqual;
|
||||
const gpa = std.testing.allocator;
|
||||
|
||||
var array_list: TestArray = .empty;
|
||||
defer array_list.deinit(gpa);
|
||||
|
||||
const id1 = try array_list.insert(gpa, 10);
|
||||
try expect(array_list.exists(id1));
|
||||
array_list.clearRetainingCapacity();
|
||||
|
||||
const id2 = try array_list.insert(gpa, 10);
|
||||
try expect(array_list.exists(id2));
|
||||
|
||||
try expectEqual(id1, id2);
|
||||
}
|
||||
1135
src/graphics.zig
1135
src/graphics.zig
File diff suppressed because it is too large
Load Diff
504
src/imgui.zig
504
src/imgui.zig
@ -1,504 +0,0 @@
|
||||
const std = @import("std");
|
||||
const Math = @import("./math.zig");
|
||||
const options = @import("options");
|
||||
pub const ig = @import("cimgui");
|
||||
const Vec2 = Math.Vec2;
|
||||
const Vec3 = Math.Vec3;
|
||||
const Vec4 = Math.Vec4;
|
||||
|
||||
const sokol = @import("sokol");
|
||||
const sapp = sokol.app;
|
||||
const simgui = sokol.imgui;
|
||||
|
||||
const enabled = options.has_imgui;
|
||||
|
||||
var global_allocator: ?std.mem.Allocator = null;
|
||||
|
||||
pub const WindowOptions = struct {
|
||||
name: [*c]const u8,
|
||||
pos: ?Vec2 = null,
|
||||
size: ?Vec2 = null,
|
||||
collapsed: ?bool = null,
|
||||
open: ?*bool = null
|
||||
};
|
||||
|
||||
pub const SliderOptions = struct {
|
||||
label: [*c]const u8,
|
||||
value: *f32,
|
||||
min: f32,
|
||||
max: f32,
|
||||
};
|
||||
|
||||
fn toImVec2(vec2: Vec2) ig.ImVec2 {
|
||||
return ig.ImVec2{
|
||||
.x = vec2.x,
|
||||
.y = vec2.y,
|
||||
};
|
||||
}
|
||||
|
||||
inline fn structCast(T: type, value: anytype) T {
|
||||
return @as(*T, @ptrFromInt(@intFromPtr(&value))).*;
|
||||
}
|
||||
|
||||
pub fn setup(gpa: std.mem.Allocator, desc: simgui.Desc) void {
|
||||
if (!enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
global_allocator = gpa;
|
||||
simgui.setup(desc);
|
||||
}
|
||||
|
||||
pub fn addFont(ttf_data: []const u8, font_size: f32) void {
|
||||
if (!enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
var font_config: ig.ImFontConfig = .{};
|
||||
font_config.FontDataOwnedByAtlas = false;
|
||||
font_config.OversampleH = 2;
|
||||
font_config.OversampleV = 2;
|
||||
font_config.GlyphMaxAdvanceX = std.math.floatMax(f32);
|
||||
font_config.RasterizerMultiply = 1.0;
|
||||
font_config.RasterizerDensity = 1.0;
|
||||
font_config.EllipsisChar = 0;
|
||||
|
||||
const io = ig.igGetIO();
|
||||
_ = ig.ImFontAtlas_AddFontFromMemoryTTF(
|
||||
io.*.Fonts,
|
||||
@constCast(@ptrCast(ttf_data.ptr)),
|
||||
@intCast(ttf_data.len),
|
||||
font_size,
|
||||
&font_config,
|
||||
null
|
||||
);
|
||||
}
|
||||
|
||||
pub fn shutdown() void {
|
||||
if (!enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
simgui.shutdown();
|
||||
}
|
||||
|
||||
pub fn handleEvent(ev: sapp.Event) bool {
|
||||
if (enabled) {
|
||||
return simgui.handleEvent(ev);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn newFrame(desc: simgui.FrameDesc) void {
|
||||
if (!enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
simgui.newFrame(desc);
|
||||
}
|
||||
|
||||
pub fn render() void {
|
||||
if (!enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
simgui.render();
|
||||
}
|
||||
|
||||
pub fn beginWindow(opts: WindowOptions) bool {
|
||||
if (!enabled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (opts.pos) |pos| {
|
||||
ig.igSetNextWindowPos(toImVec2(pos), ig.ImGuiCond_Once);
|
||||
}
|
||||
if (opts.size) |size| {
|
||||
ig.igSetNextWindowSize(toImVec2(size), ig.ImGuiCond_Once);
|
||||
}
|
||||
if (opts.collapsed) |collapsed| {
|
||||
ig.igSetNextWindowCollapsed(collapsed, ig.ImGuiCond_Once);
|
||||
}
|
||||
|
||||
ig.igSetNextWindowBgAlpha(1);
|
||||
|
||||
var open = ig.igBegin(opts.name, opts.open, ig.ImGuiWindowFlags_None);
|
||||
if (opts.open) |opts_open| {
|
||||
if (opts_open.* == false) {
|
||||
open = false;
|
||||
}
|
||||
}
|
||||
if (!open) {
|
||||
endWindow();
|
||||
}
|
||||
|
||||
return open;
|
||||
}
|
||||
|
||||
pub fn endWindow() void {
|
||||
if (!enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
ig.igEnd();
|
||||
}
|
||||
|
||||
pub fn textFmt(comptime fmt: []const u8, args: anytype) void {
|
||||
if (!enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
const gpa = global_allocator orelse return;
|
||||
|
||||
const formatted = std.fmt.allocPrintSentinel(gpa, fmt, args, 0) catch return;
|
||||
defer gpa.free(formatted);
|
||||
|
||||
text(formatted);
|
||||
}
|
||||
|
||||
pub fn text(text_z: [*c]const u8) void {
|
||||
if (!enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
ig.igText("%s", text_z);
|
||||
}
|
||||
|
||||
pub fn beginDisabled(disabled: bool) void {
|
||||
if (!enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
ig.igBeginDisabled(disabled);
|
||||
}
|
||||
|
||||
pub fn endDisabled() void {
|
||||
if (!enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
ig.igEndDisabled();
|
||||
}
|
||||
|
||||
pub fn button(label: [*c]const u8) bool {
|
||||
if (!enabled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return ig.igButton(label);
|
||||
}
|
||||
|
||||
pub fn slider(opts: SliderOptions) bool {
|
||||
if (!enabled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return ig.igSliderFloat(opts.label, opts.value, opts.min, opts.max);
|
||||
}
|
||||
|
||||
pub fn checkbox(label: [*c]const u8, value: *bool) bool {
|
||||
if (!enabled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return ig.igCheckbox(label, value);
|
||||
}
|
||||
|
||||
pub fn beginTabBar(id: [*c]const u8) bool {
|
||||
if (!enabled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return ig.igBeginTabBar(id, ig.ImGuiTabBarFlags_None);
|
||||
}
|
||||
|
||||
pub fn endTabBar() void {
|
||||
if (!enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
ig.igEndTabBar();
|
||||
}
|
||||
|
||||
pub fn beginTabItem(label: [*c]const u8) bool {
|
||||
if (!enabled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return ig.igBeginTabItem(label, null, ig.ImGuiTabItemFlags_None);
|
||||
}
|
||||
|
||||
pub fn endTabItem() void {
|
||||
if (!enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
return ig.igEndTabItem();
|
||||
}
|
||||
|
||||
pub fn beginGroup() void {
|
||||
if (!enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
ig.igBeginGroup();
|
||||
}
|
||||
|
||||
pub fn endGroup() void {
|
||||
if (!enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
ig.igEndGroup();
|
||||
}
|
||||
|
||||
pub fn sameLine() void {
|
||||
if (!enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
ig.igSameLine();
|
||||
}
|
||||
|
||||
pub fn beginTable(id: [*c]const u8, columns: u32, flags: ig.ImGuiTableFlags) bool {
|
||||
if (!enabled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return ig.igBeginTable(id, @intCast(columns), flags);
|
||||
}
|
||||
|
||||
pub fn endTable() void {
|
||||
if (!enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
ig.igEndTable();
|
||||
}
|
||||
|
||||
pub fn tableNextColumn() void {
|
||||
if (!enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
_ = ig.igTableNextColumn();
|
||||
}
|
||||
|
||||
pub fn tableNextRow() void {
|
||||
if (!enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
_ = ig.igTableNextRow();
|
||||
}
|
||||
|
||||
pub fn tableSetColumnIndex(index: usize) void {
|
||||
if (!enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
_ = ig.igTableSetColumnIndex(@intCast(index));
|
||||
}
|
||||
|
||||
pub fn tableSetupColumn(label: [*c]const u8, flags: ig.ImGuiTableColumnFlags) void {
|
||||
if (!enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
ig.igTableSetupColumn(label, flags);
|
||||
}
|
||||
|
||||
pub fn tableHeadersRow() void {
|
||||
if (!enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
ig.igTableHeadersRow();
|
||||
}
|
||||
|
||||
pub const ID = union(enum) {
|
||||
string: []const u8,
|
||||
int: i32
|
||||
};
|
||||
|
||||
pub fn pushID(id: ID) void {
|
||||
if (!enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (id) {
|
||||
.string => |str| ig.igPushIDStr(str.ptr, str.ptr + str.len),
|
||||
.int => |int| ig.igPushIDInt(int)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn popID() void {
|
||||
if (!enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
ig.igPopID();
|
||||
}
|
||||
|
||||
pub const TreeNodeFlags = packed struct {
|
||||
selected: bool = false,
|
||||
framed: bool = false,
|
||||
allow_overlap: bool = false,
|
||||
no_tree_pushOnOpen: bool = false,
|
||||
no_auto_open_on_log: bool = false,
|
||||
default_open: bool = false,
|
||||
open_on_double_click: bool = false,
|
||||
open_on_arrow: bool = false,
|
||||
leaf: bool = false,
|
||||
bullet: bool = false,
|
||||
frame_padding: bool = false,
|
||||
span_avail_width: bool = false,
|
||||
span_full_width: bool = false,
|
||||
span_label_width: bool = false,
|
||||
span_all_columns: bool = false,
|
||||
label_span_all_columns: bool = false,
|
||||
nav_left_jumps_back_here: bool = false,
|
||||
collapsing_header: bool = false,
|
||||
|
||||
fn toInt(self: TreeNodeFlags) u32 {
|
||||
// TODO: Try using comptime to reduce this duplication.
|
||||
// Would be great if `toInt()` could be replaced with just a @bitCast
|
||||
//
|
||||
// If the underlying C enum is exhaustive, maybe a bitcast could be performed?
|
||||
// If the order of enums is correct
|
||||
const flags = .{
|
||||
.{ self.selected, ig.ImGuiTreeNodeFlags_Selected },
|
||||
.{ self.framed, ig.ImGuiTreeNodeFlags_Framed },
|
||||
.{ self.allow_overlap, ig.ImGuiTreeNodeFlags_AllowOverlap },
|
||||
.{ self.no_tree_pushOnOpen, ig.ImGuiTreeNodeFlags_NoTreePushOnOpen },
|
||||
.{ self.no_auto_open_on_log, ig.ImGuiTreeNodeFlags_NoAutoOpenOnLog },
|
||||
.{ self.default_open, ig.ImGuiTreeNodeFlags_DefaultOpen },
|
||||
.{ self.open_on_double_click, ig.ImGuiTreeNodeFlags_OpenOnDoubleClick },
|
||||
.{ self.open_on_arrow, ig.ImGuiTreeNodeFlags_OpenOnArrow },
|
||||
.{ self.leaf, ig.ImGuiTreeNodeFlags_Leaf },
|
||||
.{ self.bullet, ig.ImGuiTreeNodeFlags_Bullet },
|
||||
.{ self.frame_padding, ig.ImGuiTreeNodeFlags_FramePadding },
|
||||
.{ self.span_avail_width, ig.ImGuiTreeNodeFlags_SpanAvailWidth },
|
||||
.{ self.span_full_width, ig.ImGuiTreeNodeFlags_SpanFullWidth },
|
||||
.{ self.span_label_width, ig.ImGuiTreeNodeFlags_SpanLabelWidth },
|
||||
.{ self.span_all_columns, ig.ImGuiTreeNodeFlags_SpanAllColumns },
|
||||
.{ self.label_span_all_columns, ig.ImGuiTreeNodeFlags_LabelSpanAllColumns },
|
||||
.{ self.nav_left_jumps_back_here, ig.ImGuiTreeNodeFlags_NavLeftJumpsBackHere },
|
||||
.{ self.collapsing_header, ig.ImGuiTreeNodeFlags_CollapsingHeader },
|
||||
};
|
||||
|
||||
var sum: u32 = 0;
|
||||
inline for (flags) |flag_pair| {
|
||||
if (flag_pair[0]) {
|
||||
sum += flag_pair[1];
|
||||
}
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
};
|
||||
|
||||
pub fn treeNode(label: [*c]const u8, flags: TreeNodeFlags) bool {
|
||||
if (!enabled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return ig.igTreeNodeEx(label, @intCast(flags.toInt()));
|
||||
}
|
||||
|
||||
pub fn treePop() void {
|
||||
if (!enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
ig.igTreePop();
|
||||
}
|
||||
|
||||
pub fn isItemClicked() bool {
|
||||
if (!enabled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return ig.igIsItemClicked();
|
||||
}
|
||||
|
||||
pub fn isItemToggledOpen() bool {
|
||||
if (!enabled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return ig.igIsItemToggledOpen();
|
||||
}
|
||||
|
||||
pub fn colorPicker4(label: [*c]const u8, color: *Vec4) bool {
|
||||
if (!enabled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return ig.igColorPicker4(label, color.asArray().ptr, 0, null);
|
||||
}
|
||||
|
||||
pub fn colorEdit4(label: [*c]const u8, color: *Vec4) bool {
|
||||
if (!enabled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return ig.igColorEdit4(label, color.asArray().ptr, 0);
|
||||
}
|
||||
|
||||
pub fn beginCombo(label: [*c]const u8, preview_value: [*c]const u8) bool {
|
||||
if (!enabled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return ig.igBeginCombo(label, preview_value, 0);
|
||||
}
|
||||
|
||||
pub fn endCombo() void {
|
||||
if (!enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
ig.igEndCombo();
|
||||
}
|
||||
|
||||
pub fn selectable(label: [*c]const u8, selected: bool) bool {
|
||||
if (!enabled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return ig.igSelectableEx(label, selected, 0, .{ });
|
||||
}
|
||||
|
||||
pub fn setItemDefaultFocus() void {
|
||||
if (!enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
ig.igSetItemDefaultFocus();
|
||||
}
|
||||
|
||||
pub fn combo(label: [*c]const u8, items: []const [*c]const u8, selected: *usize) void {
|
||||
if (beginCombo(label, items[selected.*])) {
|
||||
defer endCombo();
|
||||
|
||||
for (0.., items) |i, item| {
|
||||
const is_selected = selected.* == i;
|
||||
if (selectable(item, is_selected)) {
|
||||
selected.* = i;
|
||||
}
|
||||
|
||||
if (is_selected) {
|
||||
setItemDefaultFocus();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn separator() void {
|
||||
if (!enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
ig.igSeparator();
|
||||
}
|
||||
@ -1,85 +0,0 @@
|
||||
#include "stdlib.h"
|
||||
#include "stdio.h"
|
||||
#include "stdbool.h"
|
||||
#include <stdint.h>
|
||||
#define FONTSTASH_IMPLEMENTATION
|
||||
#include "fontstash.h"
|
||||
|
||||
#include "sokol/sokol_gfx.h"
|
||||
#include "sokol/sokol_gl.h"
|
||||
#define SOKOL_FONTSTASH_IMPL
|
||||
#include "sokol_fontstash.h"
|
||||
|
||||
typedef struct FONSstate FONSstate;
|
||||
|
||||
// Expose private functions so that `getTextBoundsUtf8` could be implemented in zig
|
||||
FONSstate* zig_fons__getState(FONScontext* stash)
|
||||
{
|
||||
return fons__getState(stash);
|
||||
}
|
||||
|
||||
FONSfont* zig_getFont(FONScontext* stash, int index)
|
||||
{
|
||||
if (index < 0 || index >= stash->nfonts) {
|
||||
return NULL;
|
||||
}
|
||||
FONSfont *font = stash->fonts[index];
|
||||
if (font->data == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return font;
|
||||
}
|
||||
|
||||
bool zig_isTopLeft(FONScontext* stash)
|
||||
{
|
||||
return stash->params.flags & FONS_ZERO_TOPLEFT;
|
||||
}
|
||||
|
||||
int zig_getGlyphIndex(FONSglyph* glyph)
|
||||
{
|
||||
return glyph->index;
|
||||
}
|
||||
|
||||
float zig_fons__tt_getPixelHeightScale(FONSfont *font, float size)
|
||||
{
|
||||
return fons__tt_getPixelHeightScale(&font->font, size);
|
||||
}
|
||||
|
||||
float zig_fons__getVertAlign(FONScontext* stash, FONSfont* font, int align, short isize)
|
||||
{
|
||||
return fons__getVertAlign(stash, font, align, isize);
|
||||
}
|
||||
|
||||
FONSglyph* zig_fons__getGlyph(
|
||||
FONScontext* stash,
|
||||
FONSfont* font,
|
||||
unsigned int codepoint,
|
||||
short isize,
|
||||
short iblur
|
||||
)
|
||||
{
|
||||
return fons__getGlyph(stash, font, codepoint, isize, iblur);
|
||||
}
|
||||
|
||||
void zig_fons__getQuad(
|
||||
FONScontext* stash,
|
||||
FONSfont* font,
|
||||
int prevGlyphIndex, FONSglyph* glyph,
|
||||
float scale,
|
||||
float spacing,
|
||||
float* x, float* y,
|
||||
FONSquad* q
|
||||
)
|
||||
{
|
||||
fons__getQuad(
|
||||
stash,
|
||||
font,
|
||||
prevGlyphIndex,
|
||||
glyph,
|
||||
scale,
|
||||
spacing,
|
||||
x, y,
|
||||
q
|
||||
);
|
||||
}
|
||||
@ -1,2 +0,0 @@
|
||||
#define STB_IMAGE_IMPLEMENTATION
|
||||
#include "stb_image.h"
|
||||
114
src/main.zig
114
src/main.zig
@ -1,116 +1,6 @@
|
||||
const std = @import("std");
|
||||
const tracy = @import("tracy");
|
||||
const Gfx = @import("./graphics.zig");
|
||||
const builtin = @import("builtin");
|
||||
|
||||
const Window = @import("./window.zig");
|
||||
const Event = Window.Event;
|
||||
const MouseButton = Window.MouseButton;
|
||||
|
||||
const Math = @import("./math.zig");
|
||||
const Vec2 = Math.Vec2;
|
||||
|
||||
const sokol = @import("sokol");
|
||||
const slog = sokol.log;
|
||||
const sg = sokol.gfx;
|
||||
const sapp = sokol.app;
|
||||
const sglue = sokol.glue;
|
||||
|
||||
const log = std.log.scoped(.main);
|
||||
|
||||
var gpa: std.mem.Allocator = undefined;
|
||||
|
||||
var window: Window = undefined;
|
||||
var event_queue_full_shown = false;
|
||||
|
||||
fn signalHandler(sig: i32) callconv(.c) void {
|
||||
_ = sig;
|
||||
sapp.requestQuit();
|
||||
}
|
||||
|
||||
export fn init() void {
|
||||
var zone = tracy.initZone(@src(), .{ });
|
||||
defer zone.deinit();
|
||||
|
||||
Window.init(&window, gpa) catch |e| {
|
||||
log.err("init() failed: {}", .{e});
|
||||
sapp.requestQuit();
|
||||
};
|
||||
}
|
||||
|
||||
export fn frame() void {
|
||||
tracy.frameMark();
|
||||
|
||||
const zone = tracy.initZone(@src(), .{ });
|
||||
defer zone.deinit();
|
||||
|
||||
window.frame() catch |e| {
|
||||
log.err("frame() failed: {}", .{e});
|
||||
if (@errorReturnTrace()) |trace| {
|
||||
std.debug.dumpStackTrace(trace.*);
|
||||
}
|
||||
sapp.requestQuit();
|
||||
};
|
||||
}
|
||||
|
||||
export fn cleanup() void {
|
||||
const zone = tracy.initZone(@src(), .{ });
|
||||
defer zone.deinit();
|
||||
|
||||
window.deinit();
|
||||
}
|
||||
|
||||
export fn event(e_ptr: [*c]const sapp.Event) void {
|
||||
const zone = tracy.initZone(@src(), .{ });
|
||||
defer zone.deinit();
|
||||
|
||||
const consumed_event = window.event(e_ptr) catch |e| switch (e) {
|
||||
error.EventQueueFull => blk: {
|
||||
if (!event_queue_full_shown) {
|
||||
log.warn("Event queue is full! Frame is taking too long to process", .{});
|
||||
event_queue_full_shown = true;
|
||||
}
|
||||
break :blk false;
|
||||
},
|
||||
};
|
||||
|
||||
if (consumed_event) {
|
||||
event_queue_full_shown = false;
|
||||
sapp.consumeEvent();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn main() !void {
|
||||
var debug_allocator: std.heap.DebugAllocator(.{}) = .init;
|
||||
defer _ = debug_allocator.deinit();
|
||||
|
||||
if (builtin.mode == .ReleaseFast) {
|
||||
gpa = std.heap.smp_allocator;
|
||||
} else {
|
||||
gpa = debug_allocator.allocator();
|
||||
}
|
||||
|
||||
// TODO: Use tracy TracingAllocator
|
||||
|
||||
tracy.setThreadName("Main");
|
||||
|
||||
var sa: std.posix.Sigaction = .{
|
||||
.handler = .{ .handler = signalHandler },
|
||||
.mask = std.posix.sigemptyset(),
|
||||
.flags = std.posix.SA.RESTART,
|
||||
};
|
||||
std.posix.sigaction(std.posix.SIG.INT, &sa, null);
|
||||
|
||||
sapp.run(.{
|
||||
.init_cb = init,
|
||||
.frame_cb = frame,
|
||||
.cleanup_cb = cleanup,
|
||||
.event_cb = event,
|
||||
.width = 640,
|
||||
.height = 480,
|
||||
.icon = .{ .sokol_default = true },
|
||||
.window_title = "Game",
|
||||
.logger = .{ .func = Window.sokolLogCallback },
|
||||
});
|
||||
// Prints to stderr, ignoring potential errors.
|
||||
std.debug.print("All your {s} are belong to us.\n", .{"codebase"});
|
||||
}
|
||||
|
||||
|
||||
413
src/math.zig
413
src/math.zig
@ -1,413 +0,0 @@
|
||||
const std = @import("std");
|
||||
const assert = std.debug.assert;
|
||||
|
||||
pub const bytes_per_kib = 1024;
|
||||
pub const bytes_per_mib = bytes_per_kib * 1024;
|
||||
pub const bytes_per_gib = bytes_per_mib * 1024;
|
||||
|
||||
pub const bytes_per_kb = 1000;
|
||||
pub const bytes_per_mb = bytes_per_kb * 1000;
|
||||
pub const bytes_per_gb = bytes_per_mb * 1000;
|
||||
|
||||
pub const Vec2 = extern struct {
|
||||
x: f32,
|
||||
y: f32,
|
||||
|
||||
pub const zero = init(0, 0);
|
||||
|
||||
pub fn init(x: f32, y: f32) Vec2 {
|
||||
return Vec2{
|
||||
.x = x,
|
||||
.y = y,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn initAngle(angle: f32) Vec2 {
|
||||
return Vec2{
|
||||
.x = @cos(angle),
|
||||
.y = @sin(angle),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn rotateLeft90(self: Vec2) Vec2 {
|
||||
return Vec2.init(self.y, -self.x);
|
||||
}
|
||||
|
||||
pub fn rotateRight90(self: Vec2) Vec2 {
|
||||
return Vec2.init(-self.y, self.x);
|
||||
}
|
||||
|
||||
pub fn flip(self: Vec2) Vec2 {
|
||||
return Vec2.init(-self.x, -self.y);
|
||||
}
|
||||
|
||||
pub fn add(self: Vec2, other: Vec2) Vec2 {
|
||||
return Vec2.init(
|
||||
self.x + other.x,
|
||||
self.y + other.y,
|
||||
);
|
||||
}
|
||||
|
||||
pub fn sub(self: Vec2, other: Vec2) Vec2 {
|
||||
return Vec2.init(
|
||||
self.x - other.x,
|
||||
self.y - other.y,
|
||||
);
|
||||
}
|
||||
|
||||
pub fn multiplyScalar(self: Vec2, value: f32) Vec2 {
|
||||
return Vec2.init(
|
||||
self.x * value,
|
||||
self.y * value,
|
||||
);
|
||||
}
|
||||
|
||||
pub fn multiply(self: Vec2, other: Vec2) Vec2 {
|
||||
return Vec2.init(
|
||||
self.x * other.x,
|
||||
self.y * other.y,
|
||||
);
|
||||
}
|
||||
|
||||
pub fn divide(self: Vec2, other: Vec2) Vec2 {
|
||||
return Vec2.init(
|
||||
self.x / other.x,
|
||||
self.y / other.y,
|
||||
);
|
||||
}
|
||||
|
||||
pub fn divideScalar(self: Vec2, value: f32) Vec2 {
|
||||
return Vec2.init(
|
||||
self.x / value,
|
||||
self.y / value,
|
||||
);
|
||||
}
|
||||
|
||||
pub fn length(self: Vec2) f32 {
|
||||
return @sqrt(self.x*self.x + self.y*self.y);
|
||||
}
|
||||
|
||||
pub fn distance(self: Vec2, other: Vec2) f32 {
|
||||
return self.sub(other).length();
|
||||
}
|
||||
|
||||
pub fn limitLength(self: Vec2, max_length: f32) Vec2 {
|
||||
const self_length = self.length();
|
||||
if (self_length > max_length) {
|
||||
return Vec2.init(self.x / self_length * max_length, self.y / self_length * max_length);
|
||||
} else {
|
||||
return self;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn normalized(self: Vec2) Vec2 {
|
||||
const self_length = self.length();
|
||||
if (self_length == 0) {
|
||||
return Vec2.init(0, 0);
|
||||
}
|
||||
return Vec2.init(self.x / self_length, self.y / self_length);
|
||||
}
|
||||
|
||||
pub fn initScalar(value: f32) Vec2 {
|
||||
return Vec2.init(value, value);
|
||||
}
|
||||
|
||||
pub fn eql(self: Vec2, other: Vec2) bool {
|
||||
return self.x == other.x and self.y == other.y;
|
||||
}
|
||||
|
||||
pub fn format(self: Vec2, writer: *std.io.Writer) std.io.Writer.Error!void {
|
||||
try writer.print("Vec2{{ {d}, {d} }}", .{ self.x, self.y });
|
||||
}
|
||||
};
|
||||
|
||||
pub const Vec3 = extern struct {
|
||||
x: f32, y: f32, z: f32,
|
||||
|
||||
pub const zero = init(0, 0, 0);
|
||||
|
||||
pub fn init(x: f32, y: f32, z: f32) Vec3 {
|
||||
return Vec3{
|
||||
.x = x,
|
||||
.y = y,
|
||||
.z = z,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn initScalar(value: f32) Vec3 {
|
||||
return Vec3.init(value, value, value);
|
||||
}
|
||||
|
||||
pub fn asArray(self: *Vec3) []f32 {
|
||||
const ptr: [*]f32 = @alignCast(@ptrCast(@as(*anyopaque, @ptrCast(self))));
|
||||
return ptr[0..3];
|
||||
}
|
||||
|
||||
pub fn lerp(a: Vec3, b: Vec3, t: f32) Vec3 {
|
||||
return Vec3.init(
|
||||
std.math.lerp(a.x, b.x, t),
|
||||
std.math.lerp(a.y, b.y, t),
|
||||
std.math.lerp(a.z, b.z, t),
|
||||
);
|
||||
}
|
||||
|
||||
pub fn clamp(self: Vec3, min_value: f32, max_value: f32) Vec3 {
|
||||
return Vec3.init(
|
||||
std.math.clamp(self.x, min_value, max_value),
|
||||
std.math.clamp(self.y, min_value, max_value),
|
||||
std.math.clamp(self.z, min_value, max_value),
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
pub const Vec4 = extern struct {
|
||||
x: f32, y: f32, z: f32, w: f32,
|
||||
|
||||
pub const zero = init(0, 0, 0, 0);
|
||||
|
||||
pub fn init(x: f32, y: f32, z: f32, w: f32) Vec4 {
|
||||
return Vec4{
|
||||
.x = x,
|
||||
.y = y,
|
||||
.z = z,
|
||||
.w = w
|
||||
};
|
||||
}
|
||||
|
||||
pub fn initVec3XYZ(vec3: Vec3, w: f32) Vec4 {
|
||||
return init(vec3.x, vec3.y, vec3.z, w);
|
||||
}
|
||||
|
||||
pub fn initScalar(value: f32) Vec4 {
|
||||
return Vec4.init(value, value, value, value);
|
||||
}
|
||||
|
||||
pub fn multiplyMat4(left: Vec4, right: Mat4) Vec4 {
|
||||
var result: Vec4 = undefined;
|
||||
|
||||
// TODO: SIMD
|
||||
|
||||
result.x = left.x * right.columns[0][0];
|
||||
result.y = left.x * right.columns[0][1];
|
||||
result.z = left.x * right.columns[0][2];
|
||||
result.w = left.x * right.columns[0][3];
|
||||
|
||||
result.x += left.y * right.columns[1][0];
|
||||
result.y += left.y * right.columns[1][1];
|
||||
result.z += left.y * right.columns[1][2];
|
||||
result.w += left.y * right.columns[1][3];
|
||||
|
||||
result.x += left.z * right.columns[2][0];
|
||||
result.y += left.z * right.columns[2][1];
|
||||
result.z += left.z * right.columns[2][2];
|
||||
result.w += left.z * right.columns[2][3];
|
||||
|
||||
result.x += left.w * right.columns[3][0];
|
||||
result.y += left.w * right.columns[3][1];
|
||||
result.z += left.w * right.columns[3][2];
|
||||
result.w += left.w * right.columns[3][3];
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
pub fn multiply(left: Vec4, right: Vec4) Vec4 {
|
||||
return init(
|
||||
left.x * right.x,
|
||||
left.y * right.y,
|
||||
left.z * right.z,
|
||||
left.w * right.w
|
||||
);
|
||||
}
|
||||
|
||||
pub fn asArray(self: *Vec4) []f32 {
|
||||
const ptr: [*]f32 = @alignCast(@ptrCast(@as(*anyopaque, @ptrCast(self))));
|
||||
return ptr[0..4];
|
||||
}
|
||||
|
||||
pub fn initArray(array: []const f32) Vec4 {
|
||||
return Vec4.init(array[0], array[1], array[2], array[3]);
|
||||
}
|
||||
|
||||
pub fn lerp(a: Vec4, b: Vec4, t: f32) Vec4 {
|
||||
return Vec4.init(
|
||||
std.math.lerp(a.x, b.x, t),
|
||||
std.math.lerp(a.y, b.y, t),
|
||||
std.math.lerp(a.z, b.z, t),
|
||||
std.math.lerp(a.w, b.w, t),
|
||||
);
|
||||
}
|
||||
|
||||
pub fn clamp(self: Vec4, min_value: f32, max_value: f32) Vec4 {
|
||||
return Vec4.init(
|
||||
std.math.clamp(self.x, min_value, max_value),
|
||||
std.math.clamp(self.y, min_value, max_value),
|
||||
std.math.clamp(self.z, min_value, max_value),
|
||||
std.math.clamp(self.w, min_value, max_value),
|
||||
);
|
||||
}
|
||||
|
||||
pub fn toVec3XYZ(self: Vec4) Vec3 {
|
||||
return Vec3.init(self.x, self.y, self.z);
|
||||
}
|
||||
};
|
||||
|
||||
pub const Mat4 = extern struct {
|
||||
columns: [4][4]f32,
|
||||
|
||||
pub fn initZero() Mat4 {
|
||||
var self: Mat4 = undefined;
|
||||
@memset(self.asArray(), 0);
|
||||
return self;
|
||||
}
|
||||
|
||||
pub fn initIdentity() Mat4 {
|
||||
return Mat4.initDiagonal(1);
|
||||
}
|
||||
|
||||
pub fn initDiagonal(value: f32) Mat4 {
|
||||
var self = Mat4.initZero();
|
||||
self.columns[0][0] = value;
|
||||
self.columns[1][1] = value;
|
||||
self.columns[2][2] = value;
|
||||
self.columns[3][3] = value;
|
||||
return self;
|
||||
}
|
||||
|
||||
pub fn multiply(left: Mat4, right: Mat4) Mat4 {
|
||||
var self: Mat4 = undefined;
|
||||
|
||||
inline for (.{ 0, 1, 2, 3 }) |i| {
|
||||
var column = Vec4.initArray(&right.columns[i]).multiplyMat4(left);
|
||||
@memcpy(&self.columns[i], column.asArray());
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
pub fn initScale(scale: Vec3) Mat4 {
|
||||
var self = Mat4.initIdentity();
|
||||
self.columns[0][0] = scale.x;
|
||||
self.columns[1][1] = scale.y;
|
||||
self.columns[2][2] = scale.z;
|
||||
return self;
|
||||
}
|
||||
|
||||
pub fn initTranslate(offset: Vec3) Mat4 {
|
||||
var self = Mat4.initIdentity();
|
||||
self.columns[3][0] = offset.x;
|
||||
self.columns[3][1] = offset.y;
|
||||
self.columns[3][2] = offset.z;
|
||||
return self;
|
||||
}
|
||||
|
||||
pub fn asArray(self: *Mat4) []f32 {
|
||||
const ptr: [*]f32 = @alignCast(@ptrCast(@as(*anyopaque, @ptrCast(&self.columns))));
|
||||
return ptr[0..16];
|
||||
}
|
||||
};
|
||||
|
||||
pub const Rect = struct {
|
||||
pos: Vec2,
|
||||
size: Vec2,
|
||||
|
||||
pub const zero = Rect{
|
||||
.pos = Vec2.zero,
|
||||
.size = Vec2.zero
|
||||
};
|
||||
|
||||
pub fn init(x: f32, y: f32, width: f32, height: f32) Rect {
|
||||
return Rect{
|
||||
.pos = Vec2.init(x, y),
|
||||
.size = Vec2.init(width, height)
|
||||
};
|
||||
}
|
||||
|
||||
pub fn clip(self: Rect, other: Rect) Rect {
|
||||
const left_edge = @max(self.left(), other.left());
|
||||
const right_edge = @min(self.right(), other.right());
|
||||
const top_edge = @max(self.top(), other.top());
|
||||
const bottom_edge = @min(self.bottom(), other.bottom());
|
||||
return Rect.init(
|
||||
left_edge,
|
||||
top_edge,
|
||||
right_edge - left_edge,
|
||||
bottom_edge - top_edge
|
||||
);
|
||||
}
|
||||
|
||||
pub fn left(self: Rect) f32 {
|
||||
return self.pos.x;
|
||||
}
|
||||
|
||||
pub fn right(self: Rect) f32 {
|
||||
return self.pos.x + self.size.x;
|
||||
}
|
||||
|
||||
pub fn top(self: Rect) f32 {
|
||||
return self.pos.y;
|
||||
}
|
||||
|
||||
pub fn bottom(self: Rect) f32 {
|
||||
return self.pos.y + self.size.y;
|
||||
}
|
||||
|
||||
pub fn multiply(self: Rect, xy: Vec2) Rect {
|
||||
return Rect{
|
||||
.pos = self.pos.multiply(xy),
|
||||
.size = self.size.multiply(xy),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn divide(self: Rect, xy: Vec2) Rect {
|
||||
return Rect{
|
||||
.pos = self.pos.divide(xy),
|
||||
.size = self.size.divide(xy),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn isInside(self: Rect, pos: Vec2) bool {
|
||||
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;
|
||||
return x_overlap and y_overlap;
|
||||
}
|
||||
};
|
||||
|
||||
pub const Line = struct {
|
||||
p0: Vec2,
|
||||
p1: Vec2
|
||||
};
|
||||
|
||||
pub fn isInsideRect(rect_pos: Vec2, rect_size: Vec2, pos: Vec2) bool {
|
||||
const rect = Rect{
|
||||
.pos = rect_pos,
|
||||
.size = rect_size
|
||||
};
|
||||
return rect.isInside(pos);
|
||||
}
|
||||
|
||||
pub fn rgba(r: u8, g: u8, b: u8, a: f32) Vec4 {
|
||||
assert(0 <= a and a <= 1);
|
||||
return Vec4.init(
|
||||
@as(f32, @floatFromInt(r)) / 255,
|
||||
@as(f32, @floatFromInt(g)) / 255,
|
||||
@as(f32, @floatFromInt(b)) / 255,
|
||||
a,
|
||||
);
|
||||
}
|
||||
|
||||
pub fn rgb(r: u8, g: u8, b: u8) Vec4 {
|
||||
return rgba(r, g, b, 1);
|
||||
}
|
||||
|
||||
pub fn rgb_hex(text: []const u8) ?Vec4 {
|
||||
if (text.len != 7) {
|
||||
return null;
|
||||
}
|
||||
if (text[0] != '#') {
|
||||
return null;
|
||||
}
|
||||
const r = std.fmt.parseInt(u8, text[1..3], 16) catch return null;
|
||||
const g = std.fmt.parseInt(u8, text[3..5], 16) catch return null;
|
||||
const b = std.fmt.parseInt(u8, text[5..7], 16) catch return null;
|
||||
return rgb(r, g, b);
|
||||
}
|
||||
177
src/tiled.zig
177
src/tiled.zig
@ -1,177 +0,0 @@
|
||||
const std = @import("std");
|
||||
const c = @cImport({
|
||||
@cInclude("stdlib.h");
|
||||
@cInclude("tmx.h");
|
||||
});
|
||||
|
||||
const math = @import("math.zig");
|
||||
const Vec2 = math.Vec2;
|
||||
|
||||
const log = std.log.scoped(.tiled);
|
||||
|
||||
pub const TmxError = error {
|
||||
MakeResourceManager,
|
||||
LoadTileset,
|
||||
LoadMap,
|
||||
};
|
||||
|
||||
pub fn init() void {
|
||||
c.tmx_alloc_func = c.realloc;
|
||||
c.tmx_free_func = c.free;
|
||||
}
|
||||
|
||||
pub const ResourceManager = struct {
|
||||
manager: *anyopaque,
|
||||
|
||||
pub fn init() !ResourceManager {
|
||||
const manager = c.tmx_make_resource_manager();
|
||||
if (manager == null) {
|
||||
log.err("tmx_make_resource_manager: {s}", .{c.tmx_strerr()});
|
||||
return TmxError.MakeResourceManager;
|
||||
}
|
||||
|
||||
return ResourceManager{
|
||||
.manager = manager.?,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn loadTilesetFromBuffer(self: *const ResourceManager, tileset: []const u8, key: [*:0]const u8) !void {
|
||||
const success = c.tmx_load_tileset_buffer(self.manager, tileset.ptr, @intCast(tileset.len), key);
|
||||
if (success != 1) {
|
||||
log.err("tmx_load_tileset_buffer: {s}", .{c.tmx_strerr()});
|
||||
return TmxError.LoadTileset;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn loadMapFromBuffer(self: *const ResourceManager, map: []const u8) !Map {
|
||||
const map_handle = c.tmx_rcmgr_load_buffer(self.manager, map.ptr, @intCast(map.len));
|
||||
if (map_handle == null) {
|
||||
log.err("tmx_rcmgr_load_buffer: {s}", .{c.tmx_strerr()});
|
||||
return TmxError.LoadMap;
|
||||
}
|
||||
|
||||
return Map{
|
||||
.map = map_handle.?
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(self: *const ResourceManager) void {
|
||||
c.tmx_free_resource_manager(self.manager);
|
||||
}
|
||||
};
|
||||
|
||||
pub const Tile = struct {
|
||||
tile: *c.tmx_tile,
|
||||
|
||||
pub fn getUpperLeft(self: Tile) Vec2 {
|
||||
return Vec2.init(
|
||||
@floatFromInt(self.tile.ul_x),
|
||||
@floatFromInt(self.tile.ul_y),
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
pub const TileWithFlags = struct {
|
||||
tile: Tile,
|
||||
flags: u32
|
||||
};
|
||||
|
||||
pub const Map = struct {
|
||||
map: *c.tmx_map,
|
||||
|
||||
pub fn deinit(self: *const Map) void {
|
||||
c.tmx_map_free(self.map);
|
||||
}
|
||||
|
||||
pub fn iterLayers(self: *const Map) Layer.Iterator {
|
||||
return Layer.Iterator{
|
||||
.current = self.map.ly_head
|
||||
};
|
||||
}
|
||||
|
||||
pub fn getTile(self: *const Map, layer: Layer, x: usize, y: usize) ?Tile {
|
||||
if (self.getTileWithFlags(layer, x, y)) |tile_with_flags| {
|
||||
return tile_with_flags.tile;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
pub fn getTileWithFlags(self: *const Map, layer: Layer, x: usize, y: usize) ?TileWithFlags {
|
||||
if (layer.layer.type != @intFromEnum(Layer.Type.layer)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const gid = layer.layer.content.gids[(y*self.map.width) + x];
|
||||
const flags = gid & ~FLIP_BITS_REMOVAL;
|
||||
const maybe_tile = self.map.tiles[gid & FLIP_BITS_REMOVAL];
|
||||
if (maybe_tile == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return TileWithFlags{
|
||||
.tile = Tile{ .tile = maybe_tile.? },
|
||||
.flags = flags
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
pub const Layer = struct {
|
||||
layer: *c.tmx_layer,
|
||||
|
||||
pub const Type = enum(c_uint) {
|
||||
none = c.L_NONE,
|
||||
layer = c.L_LAYER,
|
||||
object_group = c.L_OBJGR,
|
||||
image = c.L_IMAGE,
|
||||
group = c.L_GROUP
|
||||
};
|
||||
|
||||
pub const Iterator = struct {
|
||||
current: ?*c.tmx_layer,
|
||||
|
||||
pub fn next(self: *Iterator) ?Layer {
|
||||
if (self.current) |current| {
|
||||
self.current = current.next;
|
||||
return Layer{ .layer = current };
|
||||
}
|
||||
return null;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
pub const Properties = struct {
|
||||
inner: ?*c.tmx_properties,
|
||||
|
||||
pub fn getPropertyString(self: Properties, key: [*:0]const u8) ?[*:0]const u8 {
|
||||
const inner = self.inner orelse return null;
|
||||
|
||||
const maybe_prop = c.tmx_get_property(inner, key);
|
||||
if (maybe_prop == null) {
|
||||
return null;
|
||||
}
|
||||
const prop: *c.tmx_property = maybe_prop.?;
|
||||
if (prop.type != c.PT_STRING) {
|
||||
return null;
|
||||
}
|
||||
return prop.value.string;
|
||||
}
|
||||
|
||||
pub fn getPropertyBool(self: Properties, key: [*:0]const u8) ?bool {
|
||||
const inner = self.inner orelse return null;
|
||||
|
||||
const maybe_prop = c.tmx_get_property(inner, key);
|
||||
if (maybe_prop == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const prop: *c.tmx_property = maybe_prop.?;
|
||||
if (prop.type != c.PT_BOOL) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return prop.value.boolean != 0;
|
||||
}
|
||||
};
|
||||
|
||||
pub const FLIP_BITS_REMOVAL: u32 = c.TMX_FLIP_BITS_REMOVAL;
|
||||
@ -1,81 +0,0 @@
|
||||
const std = @import("std");
|
||||
const Allocator = std.mem.Allocator;
|
||||
const assert = std.debug.assert;
|
||||
|
||||
const Entity = @import("./entity.zig");
|
||||
const GenerationalArrayList = @import("./generational_array_list.zig").GenerationalArrayList;
|
||||
|
||||
const Timer = @This();
|
||||
|
||||
const ArrayList = GenerationalArrayList(Timer);
|
||||
pub const Id = ArrayList.Id;
|
||||
|
||||
pub const Options = struct {
|
||||
duration: f64,
|
||||
entity: ?Entity.Id = null
|
||||
};
|
||||
|
||||
started_at: f64,
|
||||
finishes_at: f64,
|
||||
entity: ?Entity.Id,
|
||||
|
||||
pub const List = struct {
|
||||
array_list: GenerationalArrayList(Timer),
|
||||
now: f64,
|
||||
|
||||
pub const empty = List{
|
||||
.array_list = .empty,
|
||||
.now = 0
|
||||
};
|
||||
|
||||
pub fn deinit(self: *List, gpa: Allocator) void {
|
||||
self.array_list.deinit(gpa);
|
||||
}
|
||||
|
||||
pub fn clone(self: *List, gpa: Allocator) !List {
|
||||
const array_list = try self.array_list.clone(gpa);
|
||||
errdefer array_list.deinit(gpa);
|
||||
|
||||
return List{
|
||||
.now = self.now,
|
||||
.array_list = array_list
|
||||
};
|
||||
}
|
||||
|
||||
pub fn start(self: *List, gpa: Allocator, opts: Options) !Id {
|
||||
assert(opts.duration > 0);
|
||||
return try self.array_list.insert(gpa, .{
|
||||
.started_at = self.now,
|
||||
.finishes_at = self.now + opts.duration,
|
||||
.entity = opts.entity
|
||||
});
|
||||
}
|
||||
|
||||
pub fn stop(self: *List, id: Id) void {
|
||||
_ = self.array_list.remove(id);
|
||||
}
|
||||
|
||||
pub fn running(self: *List, id: Id) bool {
|
||||
const timer = self.array_list.get(id) orelse return false;
|
||||
return timer.finishes_at > self.now;
|
||||
}
|
||||
|
||||
pub fn percent_passed(self: *List, id: Id) f32 {
|
||||
const timer = self.array_list.get(id) orelse return 0;
|
||||
|
||||
const time_passed = self.now - timer.started_at;
|
||||
const duration = timer.finishes_at - timer.started_at;
|
||||
return @floatCast(std.math.clamp(time_passed / duration, 0, 1));
|
||||
}
|
||||
|
||||
pub fn finished(self: *List, id: Id) bool {
|
||||
const timer = self.array_list.get(id) orelse return false;
|
||||
if (timer.finishes_at > self.now) {
|
||||
return false;
|
||||
}
|
||||
|
||||
self.array_list.removeAssumeExists(id);
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
553
src/window.zig
553
src/window.zig
@ -1,553 +0,0 @@
|
||||
const std = @import("std");
|
||||
const Allocator = std.mem.Allocator;
|
||||
|
||||
const sokol = @import("sokol");
|
||||
const sapp = sokol.app;
|
||||
|
||||
const Gfx = @import("./graphics.zig");
|
||||
const Tiled = @import("./tiled.zig");
|
||||
|
||||
const Math = @import("./math.zig");
|
||||
const Vec2 = Math.Vec2;
|
||||
const rgb = Math.rgb;
|
||||
|
||||
const Game = @import("./game.zig");
|
||||
|
||||
const Window = @This();
|
||||
|
||||
const log = std.log.scoped(.window);
|
||||
|
||||
pub const MouseButton = enum {
|
||||
left,
|
||||
right,
|
||||
middle,
|
||||
|
||||
pub fn fromSokol(mouse_button: sokol.app.Mousebutton) ?MouseButton {
|
||||
return switch(mouse_button) {
|
||||
.LEFT => MouseButton.left,
|
||||
.RIGHT => MouseButton.right,
|
||||
.MIDDLE => MouseButton.middle,
|
||||
else => null
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
pub const KeyCode = enum(std.math.IntFittingRange(0, sokol.app.max_keycodes-1)) {
|
||||
SPACE = 32,
|
||||
APOSTROPHE = 39,
|
||||
COMMA = 44,
|
||||
MINUS = 45,
|
||||
PERIOD = 46,
|
||||
SLASH = 47,
|
||||
_0 = 48,
|
||||
_1 = 49,
|
||||
_2 = 50,
|
||||
_3 = 51,
|
||||
_4 = 52,
|
||||
_5 = 53,
|
||||
_6 = 54,
|
||||
_7 = 55,
|
||||
_8 = 56,
|
||||
_9 = 57,
|
||||
SEMICOLON = 59,
|
||||
EQUAL = 61,
|
||||
A = 65,
|
||||
B = 66,
|
||||
C = 67,
|
||||
D = 68,
|
||||
E = 69,
|
||||
F = 70,
|
||||
G = 71,
|
||||
H = 72,
|
||||
I = 73,
|
||||
J = 74,
|
||||
K = 75,
|
||||
L = 76,
|
||||
M = 77,
|
||||
N = 78,
|
||||
O = 79,
|
||||
P = 80,
|
||||
Q = 81,
|
||||
R = 82,
|
||||
S = 83,
|
||||
T = 84,
|
||||
U = 85,
|
||||
V = 86,
|
||||
W = 87,
|
||||
X = 88,
|
||||
Y = 89,
|
||||
Z = 90,
|
||||
LEFT_BRACKET = 91,
|
||||
BACKSLASH = 92,
|
||||
RIGHT_BRACKET = 93,
|
||||
GRAVE_ACCENT = 96,
|
||||
WORLD_1 = 161,
|
||||
WORLD_2 = 162,
|
||||
ESCAPE = 256,
|
||||
ENTER = 257,
|
||||
TAB = 258,
|
||||
BACKSPACE = 259,
|
||||
INSERT = 260,
|
||||
DELETE = 261,
|
||||
RIGHT = 262,
|
||||
LEFT = 263,
|
||||
DOWN = 264,
|
||||
UP = 265,
|
||||
PAGE_UP = 266,
|
||||
PAGE_DOWN = 267,
|
||||
HOME = 268,
|
||||
END = 269,
|
||||
CAPS_LOCK = 280,
|
||||
SCROLL_LOCK = 281,
|
||||
NUM_LOCK = 282,
|
||||
PRINT_SCREEN = 283,
|
||||
PAUSE = 284,
|
||||
F1 = 290,
|
||||
F2 = 291,
|
||||
F3 = 292,
|
||||
F4 = 293,
|
||||
F5 = 294,
|
||||
F6 = 295,
|
||||
F7 = 296,
|
||||
F8 = 297,
|
||||
F9 = 298,
|
||||
F10 = 299,
|
||||
F11 = 300,
|
||||
F12 = 301,
|
||||
F13 = 302,
|
||||
F14 = 303,
|
||||
F15 = 304,
|
||||
F16 = 305,
|
||||
F17 = 306,
|
||||
F18 = 307,
|
||||
F19 = 308,
|
||||
F20 = 309,
|
||||
F21 = 310,
|
||||
F22 = 311,
|
||||
F23 = 312,
|
||||
F24 = 313,
|
||||
F25 = 314,
|
||||
KP_0 = 320,
|
||||
KP_1 = 321,
|
||||
KP_2 = 322,
|
||||
KP_3 = 323,
|
||||
KP_4 = 324,
|
||||
KP_5 = 325,
|
||||
KP_6 = 326,
|
||||
KP_7 = 327,
|
||||
KP_8 = 328,
|
||||
KP_9 = 329,
|
||||
KP_DECIMAL = 330,
|
||||
KP_DIVIDE = 331,
|
||||
KP_MULTIPLY = 332,
|
||||
KP_SUBTRACT = 333,
|
||||
KP_ADD = 334,
|
||||
KP_ENTER = 335,
|
||||
KP_EQUAL = 336,
|
||||
LEFT_SHIFT = 340,
|
||||
LEFT_CONTROL = 341,
|
||||
LEFT_ALT = 342,
|
||||
LEFT_SUPER = 343,
|
||||
RIGHT_SHIFT = 344,
|
||||
RIGHT_CONTROL = 345,
|
||||
RIGHT_ALT = 346,
|
||||
RIGHT_SUPER = 347,
|
||||
MENU = 348,
|
||||
};
|
||||
|
||||
pub const Event = union(enum) {
|
||||
mouse_pressed: struct {
|
||||
button: MouseButton,
|
||||
position: Vec2,
|
||||
},
|
||||
mouse_released: struct {
|
||||
button: MouseButton,
|
||||
position: Vec2,
|
||||
},
|
||||
mouse_move: Vec2,
|
||||
mouse_enter: Vec2,
|
||||
mouse_leave,
|
||||
mouse_scroll: Vec2,
|
||||
key_pressed: struct {
|
||||
code: KeyCode,
|
||||
repeat: bool
|
||||
},
|
||||
key_released: KeyCode,
|
||||
window_resize,
|
||||
char: u21,
|
||||
};
|
||||
|
||||
pub const KeyState = struct {
|
||||
down: bool,
|
||||
pressed: bool,
|
||||
released: bool,
|
||||
down_duration: ?f64,
|
||||
|
||||
pub const RepeatOptions = struct {
|
||||
first_at: f64 = 0,
|
||||
period: f64
|
||||
};
|
||||
|
||||
pub fn repeat(self: KeyState, last_repeat_at: *?f64, opts: RepeatOptions) bool {
|
||||
if (!self.down) {
|
||||
last_repeat_at.* = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
const down_duration = self.down_duration.?;
|
||||
if (last_repeat_at.* != null) {
|
||||
if (down_duration >= last_repeat_at.*.? + opts.period) {
|
||||
last_repeat_at.* = last_repeat_at.*.? + opts.period;
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
if (down_duration >= opts.first_at) {
|
||||
last_repeat_at.* = opts.first_at;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
const Nanoseconds = i128;
|
||||
|
||||
gpa: Allocator,
|
||||
events: std.ArrayList(Event),
|
||||
mouse_inside: bool = false,
|
||||
game: Game,
|
||||
|
||||
last_frame_at_ns: Nanoseconds,
|
||||
frame_dt_ns: Nanoseconds,
|
||||
time_ns: Nanoseconds,
|
||||
|
||||
down_keys: std.EnumSet(KeyCode) = .initEmpty(),
|
||||
pressed_keys: std.EnumSet(KeyCode) = .initEmpty(),
|
||||
released_keys: std.EnumSet(KeyCode) = .initEmpty(),
|
||||
pressed_keys_at: std.EnumMap(KeyCode, Nanoseconds) = .init(.{}),
|
||||
|
||||
pub fn init(self: *Window, gpa: Allocator) !void {
|
||||
Tiled.init();
|
||||
|
||||
var events: std.ArrayList(Event) = .empty;
|
||||
errdefer events.deinit(gpa);
|
||||
try events.ensureTotalCapacityPrecise(gpa, 50);
|
||||
|
||||
try Gfx.init(.{
|
||||
.allocator = gpa,
|
||||
.logger = .{ .func = sokolLogCallback }
|
||||
});
|
||||
|
||||
var game = try Game.init(gpa);
|
||||
errdefer game.deinit();
|
||||
|
||||
self.* = Window{
|
||||
.gpa = gpa,
|
||||
.events = events,
|
||||
.last_frame_at_ns = std.time.nanoTimestamp(),
|
||||
.frame_dt_ns = 0,
|
||||
.time_ns = 0,
|
||||
.game = game
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Window) void {
|
||||
const gpa = self.gpa;
|
||||
|
||||
self.game.deinit();
|
||||
self.events.deinit(gpa);
|
||||
|
||||
Gfx.deinit();
|
||||
}
|
||||
|
||||
pub fn frame(self: *Window) !void {
|
||||
const now = std.time.nanoTimestamp();
|
||||
self.frame_dt_ns = now - self.last_frame_at_ns;
|
||||
self.last_frame_at_ns = now;
|
||||
self.time_ns += self.frame_dt_ns;
|
||||
|
||||
Gfx.beginFrame();
|
||||
defer Gfx.endFrame();
|
||||
|
||||
self.pressed_keys = .initEmpty();
|
||||
self.released_keys = .initEmpty();
|
||||
for (self.events.items) |e| {
|
||||
switch (e) {
|
||||
.key_pressed => |opts| {
|
||||
if (!opts.repeat) {
|
||||
self.pressed_keys_at.put(opts.code, self.time_ns);
|
||||
self.pressed_keys.insert(opts.code);
|
||||
self.down_keys.insert(opts.code);
|
||||
}
|
||||
},
|
||||
.key_released => |key_code| {
|
||||
self.down_keys.remove(key_code);
|
||||
self.released_keys.insert(key_code);
|
||||
self.pressed_keys_at.remove(key_code);
|
||||
},
|
||||
.mouse_leave => {
|
||||
var iter = self.down_keys.iterator();
|
||||
while (iter.next()) |key_code| {
|
||||
self.released_keys.insert(key_code);
|
||||
}
|
||||
self.down_keys = .initEmpty();
|
||||
self.pressed_keys_at = .init(.{});
|
||||
},
|
||||
else => {}
|
||||
}
|
||||
}
|
||||
self.events.clearRetainingCapacity();
|
||||
|
||||
// TODO: Render to a lower resolution instead of scaling.
|
||||
// To avoid pixel bleeding in spritesheet artifacts
|
||||
const window_size: Vec2 = .init(sapp.widthf(), sapp.heightf());
|
||||
const scale = @floor(@min(
|
||||
window_size.x / self.game.canvas_size.x,
|
||||
window_size.y / self.game.canvas_size.y,
|
||||
));
|
||||
|
||||
var filler_size: Vec2 = Vec2.sub(window_size, self.game.canvas_size.multiplyScalar(scale)).multiplyScalar(0.5);
|
||||
filler_size.x = @round(filler_size.x);
|
||||
filler_size.y = @round(filler_size.y);
|
||||
|
||||
const input = self.game.getInput(self);
|
||||
|
||||
{
|
||||
Gfx.pushTransform(filler_size, scale);
|
||||
defer Gfx.popTransform();
|
||||
|
||||
try self.game.tick(input);
|
||||
}
|
||||
|
||||
const bg_color = rgb(0, 0, 0);
|
||||
|
||||
Gfx.drawRectangle(
|
||||
.init(0, 0),
|
||||
.init(window_size.x, filler_size.y),
|
||||
bg_color
|
||||
);
|
||||
|
||||
Gfx.drawRectangle(
|
||||
.init(0, window_size.y - filler_size.y),
|
||||
.init(window_size.x, filler_size.y),
|
||||
bg_color
|
||||
);
|
||||
|
||||
Gfx.drawRectangle(
|
||||
.init(0, 0),
|
||||
.init(filler_size.x, window_size.y),
|
||||
bg_color
|
||||
);
|
||||
|
||||
Gfx.drawRectangle(
|
||||
.init(window_size.x - filler_size.x, 0),
|
||||
.init(filler_size.x, window_size.y),
|
||||
bg_color
|
||||
);
|
||||
|
||||
try self.game.debug();
|
||||
}
|
||||
|
||||
pub fn isKeyDown(self: *Window, key_code: KeyCode) bool {
|
||||
return self.down_keys.contains(key_code);
|
||||
}
|
||||
|
||||
pub fn getKeyDownDuration(self: *Window, key_code: KeyCode) ?f64 {
|
||||
if (!self.isKeyDown(key_code)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const pressed_at_ns = self.pressed_keys_at.get(key_code).?;
|
||||
const duration_ns = self.time_ns - pressed_at_ns;
|
||||
|
||||
return @as(f64, @floatFromInt(duration_ns)) / std.time.ns_per_s;
|
||||
}
|
||||
|
||||
pub fn isKeyPressed(self: *Window, key_code: KeyCode) bool {
|
||||
return self.pressed_keys.contains(key_code);
|
||||
}
|
||||
|
||||
pub fn isKeyReleased(self: *Window, key_code: KeyCode) bool {
|
||||
return self.released_keys.contains(key_code);
|
||||
}
|
||||
|
||||
pub fn getKeyState(self: *Window, key_code: KeyCode) KeyState {
|
||||
return KeyState{
|
||||
.down = self.isKeyDown(key_code),
|
||||
.released = self.isKeyReleased(key_code),
|
||||
.pressed = self.isKeyPressed(key_code),
|
||||
.down_duration = self.getKeyDownDuration(key_code)
|
||||
};
|
||||
}
|
||||
|
||||
fn appendEvent(self: *Window, e: Event) !void {
|
||||
self.events.appendBounded(e) catch return error.EventQueueFull;
|
||||
}
|
||||
|
||||
pub fn event(self: *Window, e_ptr: [*c]const sapp.Event) !bool {
|
||||
if (Gfx.event(e_ptr)) {
|
||||
try self.appendEvent(Event{
|
||||
.mouse_leave = {}
|
||||
});
|
||||
self.mouse_inside = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
const e = e_ptr.*;
|
||||
blk: switch (e.type) {
|
||||
.MOUSE_DOWN => {
|
||||
const mouse_button = Window.MouseButton.fromSokol(e.mouse_button) orelse break :blk;
|
||||
|
||||
try self.appendEvent(Event{
|
||||
.mouse_pressed = .{
|
||||
.button = mouse_button,
|
||||
.position = Vec2.init(e.mouse_x, e.mouse_y)
|
||||
}
|
||||
});
|
||||
|
||||
return true;
|
||||
},
|
||||
.MOUSE_UP => {
|
||||
const mouse_button = MouseButton.fromSokol(e.mouse_button) orelse break :blk;
|
||||
|
||||
try self.appendEvent(Event{
|
||||
.mouse_released = .{
|
||||
.button = mouse_button,
|
||||
.position = Vec2.init(e.mouse_x, e.mouse_y)
|
||||
}
|
||||
});
|
||||
|
||||
return true;
|
||||
},
|
||||
.MOUSE_MOVE => {
|
||||
if (!self.mouse_inside) {
|
||||
try self.appendEvent(Event{
|
||||
.mouse_enter = Vec2.init(e.mouse_x, e.mouse_y)
|
||||
});
|
||||
}
|
||||
|
||||
try self.appendEvent(Event{
|
||||
.mouse_move = Vec2.init(e.mouse_x, e.mouse_y)
|
||||
});
|
||||
|
||||
self.mouse_inside = true;
|
||||
return true;
|
||||
},
|
||||
.MOUSE_ENTER => {
|
||||
if (!self.mouse_inside) {
|
||||
try self.appendEvent(Event{
|
||||
.mouse_enter = Vec2.init(e.mouse_x, e.mouse_y)
|
||||
});
|
||||
}
|
||||
|
||||
self.mouse_inside = true;
|
||||
return true;
|
||||
},
|
||||
.RESIZED => {
|
||||
if (self.mouse_inside) {
|
||||
try self.appendEvent(Event{
|
||||
.mouse_leave = {}
|
||||
});
|
||||
}
|
||||
|
||||
try self.appendEvent(Event{
|
||||
.window_resize = {}
|
||||
});
|
||||
|
||||
self.mouse_inside = false;
|
||||
return true;
|
||||
},
|
||||
.MOUSE_LEAVE => {
|
||||
if (self.mouse_inside) {
|
||||
try self.appendEvent(Event{
|
||||
.mouse_leave = {}
|
||||
});
|
||||
}
|
||||
|
||||
self.mouse_inside = false;
|
||||
return true;
|
||||
},
|
||||
.MOUSE_SCROLL => {
|
||||
try self.appendEvent(Event{
|
||||
.mouse_scroll = Vec2.init(e.scroll_x, e.scroll_y)
|
||||
});
|
||||
|
||||
return true;
|
||||
},
|
||||
.KEY_DOWN => {
|
||||
try self.appendEvent(Event{
|
||||
.key_pressed = .{
|
||||
.code = @enumFromInt(@intFromEnum(e.key_code)),
|
||||
.repeat = e.key_repeat
|
||||
}
|
||||
});
|
||||
|
||||
return true;
|
||||
},
|
||||
.KEY_UP => {
|
||||
try self.appendEvent(Event{
|
||||
.key_released = @enumFromInt(@intFromEnum(e.key_code))
|
||||
});
|
||||
|
||||
return true;
|
||||
},
|
||||
.CHAR => {
|
||||
try self.appendEvent(Event{
|
||||
.char = @intCast(e.char_code)
|
||||
});
|
||||
|
||||
return true;
|
||||
},
|
||||
.QUIT_REQUESTED => {
|
||||
// TODO: handle quit request. Maybe show confirmation window in certain cases.
|
||||
},
|
||||
else => {}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
fn cStrToZig(c_str: [*c]const u8) [:0]const u8 {
|
||||
return std.mem.span(c_str);
|
||||
}
|
||||
|
||||
fn sokolLogFmt(log_level: u32, comptime format: []const u8, args: anytype) void {
|
||||
const log_sokol = std.log.scoped(.sokol);
|
||||
|
||||
if (log_level == 0) {
|
||||
log_sokol.err(format, args);
|
||||
} else if (log_level == 1) {
|
||||
log_sokol.err(format, args);
|
||||
} else if (log_level == 2) {
|
||||
log_sokol.warn(format, args);
|
||||
} else {
|
||||
log_sokol.info(format, args);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn sokolLogCallback(tag: [*c]const u8, log_level: u32, log_item: u32, message: [*c]const u8, line_nr: u32, filename: [*c]const u8, user_data: ?*anyopaque) callconv(.c) void {
|
||||
_ = user_data;
|
||||
|
||||
if (filename != null) {
|
||||
const format = "[{s}][id:{}] {s}:{}: {s}";
|
||||
const args = .{
|
||||
cStrToZig(tag orelse "-"),
|
||||
log_item,
|
||||
std.fs.path.basename(cStrToZig(filename orelse "-")),
|
||||
line_nr,
|
||||
cStrToZig(message orelse "")
|
||||
};
|
||||
|
||||
sokolLogFmt(log_level, format, args);
|
||||
} else {
|
||||
const format = "[{s}][id:{}] {s}";
|
||||
const args = .{
|
||||
cStrToZig(tag orelse "-"),
|
||||
log_item,
|
||||
cStrToZig(message orelse "")
|
||||
};
|
||||
|
||||
sokolLogFmt(log_level, format, args);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user