diff --git a/.gitignore b/.gitignore
index f44b7f66..2b0e825d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,7 +1,32 @@
+# Source temporaries
+*.cpp~
+*.hpp~
+src/.*.cpp.swp*
+src/.*.hpp.swp*
+src/*.cpp~
+src/*.hpp~
+src/common/*.cpp.swp*
+src/common/*.hpp.swp*
+src/common/*.cpp~
+src/common/*.hpp~
+src/sound/*.cpp.swp*
+src/sound/*.hpp.swp*
+src/sound/*.cpp~
+src/sound/*.hpp~
+src/server/*.cpp.swp*
+src/server/*.hpp.swp*
+src/server/*.cpp~
+src/server/*.hpp~
+src/net/*.cpp.swp*
+src/net/*.hpp.swp*
+src/net/*.cpp~
+src/net/*.hpp~
+
# Windows binaries
*.exe
*.dll
-
+*a.out
+*.so
# QtCreator project user settings
*.user
@@ -20,17 +45,44 @@ build-*
*.cfg
# Game data
-csm.bin
-CSM.BIN
-CSM.bin
-csm.BIN
+*.bin
+*.BIN
+*.tar
+*.TAR
+CURSED/
+CURSED/*
+cursed/
+cursed/*
+MUSIC/
+MUSIC/*
+music/
+music/*
+ADDON1/
+ADDON1/*
+addon1/
+addon1/*
+borough/
+borough/*
+BOROUGH/
+BOROUGH/*
+USERMAP/
+USERMAP/*
+usermap/
+usermap/*
# Game saves
saves/*
+SAVES/*
+
+# Screen shots
+*.tga
+*.TGA
# Build system
CMakeFiles/*
CMakeFiles
+CMakeDoxyfile.in
+CMakeDoxygenDefaults.cmake
cmake_install.cmake
install_manifest.txt
CMakeCache.txt
@@ -44,3 +96,4 @@ MapToTGAConverter
ObjToTGAConverter
PaletteExtractor
PanzerChasm
+compile_commands.json
diff --git a/.gitmodules b/.gitmodules
index 5c38eae6..942421e6 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -4,3 +4,9 @@
[submodule "src/panzer_ogl_lib"]
path = src/panzer_ogl_lib
url = http://github.com/Panzerschrek/panzer_ogl_lib
+[submodule "external/meshoptimizer"]
+ path = external/meshoptimizer
+ url = https://github.com/zeux/meshoptimizer
+[submodule "external/physfs"]
+ path = external/physfs
+ url = https://github.com/jopadan/physfs/
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 975cb0d9..7c272aaa 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,8 +1,18 @@
-cmake_minimum_required(VERSION 3.1)
+cmake_minimum_required(VERSION 3.10)
project(Chasm-Reverse)
-set(CMAKE_CXX_STANDARD 11)
+include(CheckCXXSourceCompiles)
+include(GNUInstallDirs)
+
+set(CMAKE_CXX_STANDARD 17)
+set(CMAKE_CXX_STANDARD_REQUIRED YES)
+
+if(${CMAKE_CXX_COMPILER_FRONTEND_VARIANT} STREQUAL "GNU")
+set(CMAKE_CXX_EXTENSIONS ON)
+endif()
+
+set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake)
if(MSVC)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /D _CRT_SECURE_NO_WARNINGS /MP")
@@ -10,21 +20,25 @@ if(MSVC)
endif()
option(BUILD_TOOLS "Enable compilation of tools" YES)
-include(CheckCXXSourceCompiles)
-include(GNUInstallDirs)
-set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake)
+add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/external/physfs)
+
find_package(SDL2 REQUIRED)
include_directories(${SDL2_INCLUDE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}
- ${CMAKE_CURRENT_SOURCE_DIR}/src/ ${CMAKE_CURRENT_SOURCE_DIR}/src/panzer_ogl_lib)
+ ${CMAKE_CURRENT_SOURCE_DIR}/src/
+ ${CMAKE_CURRENT_SOURCE_DIR}/src/panzer_ogl_lib
+ ${CMAKE_CURRENT_SOURCE_DIR}/external/physfs/src
+ ${CMAKE_CURRENT_SOURCE_DIR}/external/cgltf)
# Source and header files
file(GLOB_RECURSE CHASM_SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp")
file(GLOB_RECURSE CHASM_HEADERS
"${CMAKE_CURRENT_SOURCE_DIR}/src/*.hpp"
"${CMAKE_CURRENT_SOURCE_DIR}/src/*.h"
- "${CMAKE_CURRENT_SOURCE_DIR}/src/*.inl")
+ "${CMAKE_CURRENT_SOURCE_DIR}/src/*.inl"
+ "${CMAKE_CURRENT_SOURCE_DIR}/external/physfs/src/*.h"
+ "${CMAKE_CURRENT_SOURCE_DIR}/external/meshoptimzer/cgltf/extern/*.h")
# Detect MMX support
@@ -48,6 +62,7 @@ endif()
set(CHASM_LIBS
${SDL2_LIBRARY}
+ physfs
)
if(WIN32)
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 00000000..f288702d
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,674 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc.
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users. We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors. You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights. Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received. You must make sure that they, too, receive
+or can get the source code. And you must show them these terms so they
+know their rights.
+
+ Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+ For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software. For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+ Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so. This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software. The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable. Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products. If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+ Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary. To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Use with the GNU Affero General Public License.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+
+ Copyright (C)
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+ Copyright (C)
+ This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+.
+
+ The GNU General Public License does not permit incorporating your program
+into proprietary programs. If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License. But first, please read
+.
diff --git a/README.md b/README.md
index 15d7f4a6..cc66a213 100644
--- a/README.md
+++ b/README.md
@@ -20,18 +20,24 @@ https://en.wikipedia.org/wiki/Chasm:_The_Rift.
### Building
-Install/extract the GOG version and mount the bin/cue image at
+Install/extract the GOG version to
-`GOG\ Games\Chasm\ The\ Rift/Chasm\ The\ Rift\ Original/CHASMPR.CUE`
+`GOG_INSTALL_PATH=C:\GOG\ Games\Chasm\ The\ Rift/`
- in cdemu or dosbox and install running `dossetup.exe` to `DOS_INSTALL_PATH=C:\CHASM`.
+mount the bin/cue image at
+
+`$GOG_INSTALL_PATH/Chasm\ The\ Rift\ Original/CHASMPR.CUE`
+
+in cdemu or dosbox and install running `dossetup.exe` to `DOS_INSTALL_PATH=C:\CHASM`.
```sh
git clone --recurse-submodules --recursive http://github.com/Panzerschrek/Chasm-Reverse
cd Chasm-Reverse
cp -ar $DOS_INSTALL_PATH/CSM.BIN .
+cp -ar $GOG_INSTALL_PATH/csm.bin csm.tar
cmake .
```
+
### Installation
Copy CSM.BIN from the original game folder and shaders/ from the Chasm-Reverse to the destination directory DESTDIR
@@ -52,37 +58,26 @@ the command option `--csm` strings, for example:
`./PanzerChasm --csm CSM_RUS.BIN`
-To run the add-on, you must additionally specify the path to it through the
-parameter command line `--addon`, for example:
-
-`./PanzerChasm --addon ADDON1`
-
In order to execute some console command at start, use `--exec` option, for example:
`./PanzerChasm --exec "load saves/save_00.pcs"` to start game and immediately load first saved game.
+#### Available add-ons:
-#### Control
-
-##### Keyboard
-
-* WASD - movement
-* SPACE - jump
-* digits 1-8 - weapon selection
-* "~" - open/close the console
-* ESC - menu.
-* TAB - auto complete commands at the console
+* Chasm - The Shadow Zone: ADDON1
+* Chasm - Cursed Land : cursed
+* Chasm - Grim Borough : borough
-##### Mouse
-
-* left mouse button - fire/(select menu)
-* middle mouse button - weapon next
-* right mouse button - jump/(back/escape menu)
-* mouse wheel up/down - weapon next/previous
+To run the add-on, you must additionally specify the path to it through the
+parameter command line `--addon` or `-addon`, for example:
-Part of the control can be changed in the settings menu.
+`./PanzerChasm --addon ADDON1`
+### Resources
+* [AwesomeChasm](https://github.com/jopadan/awesomeChasm)
+* [Shikadi Modding Wiki](https://moddingwiki.shikadi.net/wiki/Chasm:_The_Rift)
+* [The Shadow Zone](https://discord.com/channels/768103789411434586/1374778669612007527)
### Authors
Copyright © 2016-2023 Artöm "Panzerschrek" Kunz, github contributors.
diff --git a/default_PanzerChasm.cfg b/default_PanzerChasm.cfg
index 19dddc24..f9a52fe6 100644
--- a/default_PanzerChasm.cfg
+++ b/default_PanzerChasm.cfg
@@ -43,6 +43,6 @@
"r_software_use_gl_screen_update" "0"
"r_window_height" "600"
"r_window_width" "800"
-"s_nosound" "0"
+"s_nosound" "1"
"s_volume" "0.500000"
"sv_always_run" "1"
diff --git a/external/meshoptimizer b/external/meshoptimizer
new file mode 160000
index 00000000..52b4665f
--- /dev/null
+++ b/external/meshoptimizer
@@ -0,0 +1 @@
+Subproject commit 52b4665f6506f57d8c97016f02b00e5a5209f5e8
diff --git a/external/physfs b/external/physfs
new file mode 160000
index 00000000..43fc3585
--- /dev/null
+++ b/external/physfs
@@ -0,0 +1 @@
+Subproject commit 43fc3585d0076baea6c776d62b503a96a957bd01
diff --git a/src/client/client.cpp b/src/client/client.cpp
index 17f23213..a067350d 100644
--- a/src/client/client.cpp
+++ b/src/client/client.cpp
@@ -1,3 +1,4 @@
+#include
#include "../assert.hpp"
#include "../game_constants.hpp"
#include "../i_drawers_factory.hpp"
@@ -16,8 +17,9 @@
#include "i_hud_drawer.hpp"
#include "i_map_drawer.hpp"
#include "i_minimap_drawer.hpp"
-
+#include "../key_checker.hpp"
#include "client.hpp"
+#include "../common/str.hpp"
namespace PanzerChasm
{
@@ -138,8 +140,18 @@ void Client::Save( SaveLoadBuffer& buffer, SaveComment& out_save_comment )
for( const bool& wall_visibility : dynamic_walls_visibility )
save_stream.WriteBool( wall_visibility );
- // Write comment
- std::snprintf( out_save_comment.data(), sizeof(SaveComment), "Level%2d health %03d", current_map_data_->number, player_state_.health );
+
+
+ // Write timestamp or level/health to comment
+ if( StringEquals(settings_.GetString( SettingsKeys::save_comment, "time" ), "time") )
+ {
+ std::time_t timestamp = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
+ std::snprintf( out_save_comment.data(), sizeof(SaveComment), "%s", ctime( ×tamp ) + 4);
+ }
+ else
+ {
+ std::snprintf( out_save_comment.data(), sizeof(SaveComment), "Level%2d health %03d", current_map_data_->number, player_state_.health );
+ }
}
void Client::Load( const SaveLoadBuffer& buffer, unsigned int& buffer_pos )
@@ -222,36 +234,30 @@ void Client::ProcessEvents( const SystemEvents& events )
event.event.mouse_move.dx );
}
- // Select weapon.
- if( event.type == SystemEvent::Type::Wheel && event.event.wheel.delta != 0 )
+ if( event.type == SystemEvent::Type::Key || event.type == SystemEvent::Type::MouseKey || event.type == SystemEvent::Type::Wheel)
{
- if(event.event.wheel.delta > 0) TrySwitchWeaponNext();
- if(event.event.wheel.delta < 0) TrySwitchWeaponPrevious();
- }
-
- if( event.type == SystemEvent::Type::MouseKey &&
- event.event.mouse_key.mouse_button == SystemEvent::MouseKeyEvent::Button::Middle )
- {
- TrySwitchWeaponNext();
- }
-
- if( event.type == SystemEvent::Type::Key && event.event.key.pressed )
- {
- if( event.event.key.key_code >= KeyCode::K1 &&
- static_cast(event.event.key.key_code) < static_cast(KeyCode::K1) + GameConstants::weapon_count )
+ const KeyChecker key_pressed( settings_, event );
+
+ if( key_pressed( SettingsKeys::key_weapon_next, KeyCode::MouseWheelDown ) ) TrySwitchWeaponNext();
+ if( key_pressed( SettingsKeys::key_weapon_prev, KeyCode::MouseWheelUp ) ) TrySwitchWeaponPrevious();
+ if( key_pressed( SettingsKeys::key_weapon_change, KeyCode::Mouse3 ) ) TrySwitchWeaponNext();
+ if( key_pressed( SettingsKeys::key_weapon_1, KeyCode::K1 ) ||
+ key_pressed( SettingsKeys::key_weapon_2, KeyCode::K2 ) ||
+ key_pressed( SettingsKeys::key_weapon_3, KeyCode::K3 ) ||
+ key_pressed( SettingsKeys::key_weapon_4, KeyCode::K4 ) ||
+ key_pressed( SettingsKeys::key_weapon_5, KeyCode::K5 ) ||
+ key_pressed( SettingsKeys::key_weapon_6, KeyCode::K6 ) ||
+ key_pressed( SettingsKeys::key_weapon_7, KeyCode::K7 ) ||
+ key_pressed( SettingsKeys::key_weapon_8, KeyCode::K8 ) ||
+ key_pressed( SettingsKeys::key_weapon_9, KeyCode::K9 ) )
{
unsigned int weapon_index=static_cast( event.event.key.key_code ) - static_cast( KeyCode::K1 );
if( player_state_.ammo[ weapon_index ] > 0u && ( player_state_.weapons_mask & (1u << weapon_index) ) != 0u )
requested_weapon_index_= weapon_index;
}
-
- if( event.event.key.key_code == KeyCode::Tab )
- minimap_mode_= !minimap_mode_;
-
- if( event.event.key.key_code == KeyCode::Minus )
- settings_.SetSetting( g_small_hud_mode, false );
- if( event.event.key.key_code == KeyCode::Equals )
- settings_.SetSetting( g_small_hud_mode, true );
+ if( key_pressed( SettingsKeys::key_minimap, KeyCode::Tab ) ) minimap_mode_= !minimap_mode_;
+ if( key_pressed( SettingsKeys::key_small_hud_off, KeyCode::Minus ) ) settings_.SetSetting( g_small_hud_mode, false );
+ if( key_pressed( SettingsKeys::key_small_hud_on, KeyCode::Equals ) ) settings_.SetSetting( g_small_hud_mode, true );
}
} // for events
}
@@ -259,6 +265,7 @@ void Client::ProcessEvents( const SystemEvents& events )
void Client::Loop( const InputState& input_state, const bool paused )
{
const Time current_real_time= Time::CurrentTime();
+ const KeyChecker key_pressed( settings_, input_state );
// Calculate time, which we spend in pause.
// Subtract time, spended in pauses, from real time.
@@ -306,14 +313,16 @@ void Client::Loop( const InputState& input_state, const bool paused )
{ // Scale minimap.
const float log2_delta= 2.0f * tick_dt_s;
- if( input_state.keyboard[ static_cast( SystemEvent::KeyEvent::KeyCode::SquareBrackretLeft ) ] )
+
+ if( key_pressed( SettingsKeys::key_minimap_scale_dec, KeyCode::SquareBracketLeft ) )
minimap_scale_log2_-= log2_delta;
- if( input_state.keyboard[ static_cast( SystemEvent::KeyEvent::KeyCode::SquareBrackretRight ) ] )
+ if( key_pressed( SettingsKeys::key_minimap_scale_inc, KeyCode::SquareBracketRight ) )
minimap_scale_log2_+= log2_delta;
+
minimap_scale_log2_= std::max( -2.0f, std::min( minimap_scale_log2_, 1.0f ) );
}
- camera_controller_.Tick( input_state.keyboard );
+ camera_controller_.Tick( input_state );
if( sound_engine_ != nullptr )
{
@@ -355,17 +364,17 @@ void Client::Loop( const InputState& input_state, const bool paused )
}
{ // Send move message
float move_direction, move_acceleration;
- camera_controller_.GetAcceleration( input_state.keyboard, move_direction, move_acceleration );
+ camera_controller_.GetAcceleration( input_state, move_direction, move_acceleration );
Messages::PlayerMove message;
message.view_direction = AngleToMessageAngle( camera_controller_.GetViewAngleZ() + Constants::half_pi );
message.move_direction = AngleToMessageAngle( move_direction );
message.acceleration = static_cast( move_acceleration * 254.5f );
- message.jump_pressed = input_state.mouse[ static_cast( SystemEvent::MouseKeyEvent::Button::Right ) ] || camera_controller_.JumpPressed();
+ message.jump_pressed = key_pressed( SettingsKeys::key_jump, KeyCode::Mouse2 ) || camera_controller_.JumpPressed();
message.weapon_index = requested_weapon_index_;
message.view_dir_angle_x = AngleToMessageAngle( camera_controller_.GetViewAngleX() );
message.view_dir_angle_z = AngleToMessageAngle( camera_controller_.GetViewAngleZ() );
- message.shoot_pressed = input_state.mouse[ static_cast( SystemEvent::MouseKeyEvent::Button::Left ) ];
+ message.shoot_pressed = key_pressed( SettingsKeys::key_fire, KeyCode::Mouse1 );
message.color = settings_.GetOrSetInt( SettingsKeys::player_color );
connection_info_->messages_sender.SendUnreliableMessage( message );
}
diff --git a/src/client/movement_controller.cpp b/src/client/movement_controller.cpp
index f17f25b9..e7fe9345 100644
--- a/src/client/movement_controller.cpp
+++ b/src/client/movement_controller.cpp
@@ -3,36 +3,14 @@
#include "game_constants.hpp"
#include "settings.hpp"
#include "shared_settings_keys.hpp"
-
#include "movement_controller.hpp"
+#include "../key_checker.hpp"
namespace PanzerChasm
{
static const float g_z_near= 1.0f / 12.0f; // Must be greater, then z_near in software rasterizer.
-using KeyCode= SystemEvent::KeyEvent::KeyCode;
-
-class KeyChecker
-{
-public:
- KeyChecker( Settings& settings, const KeyboardState& keyboard_state )
- : settings_(settings), keyboard_state_(keyboard_state)
- {}
-
- bool operator()( const char* const key_setting_name, const KeyCode default_value ) const
- {
- const KeyCode key= static_cast( settings_.GetOrSetInt( key_setting_name, static_cast(default_value) ) );
- if( key > KeyCode::Unknown && key < KeyCode::KeyCount )
- return keyboard_state_[ static_cast( key ) ];
- return false;
- }
-
-private:
- Settings& settings_;
- const KeyboardState& keyboard_state_;
-};
-
MovementController::MovementController(
Settings& settings,
const m_Vec3& angle,
@@ -40,6 +18,8 @@ MovementController::MovementController(
: settings_( settings )
, angle_(angle), aspect_(aspect)
, speed_(0.0f)
+ , mouse_look_( settings_.GetOrSetBool( SettingsKeys::perm_mlook, true ) )
+ , mouse_look_pressed_( false )
, start_tick_( Time::CurrentTime() )
, prev_calc_tick_( Time::CurrentTime() )
{
@@ -65,7 +45,7 @@ void MovementController::UpdateParams()
ClipCameraAngles();
}
-void MovementController::Tick( const KeyboardState& keyboard_state )
+void MovementController::Tick( const InputState& input_state )
{
const Time new_tick= Time::CurrentTime();
@@ -73,20 +53,24 @@ void MovementController::Tick( const KeyboardState& keyboard_state )
prev_calc_tick_= new_tick;
- const KeyChecker key_pressed( settings_,keyboard_state );
+ const KeyChecker key_pressed( settings_, input_state );
m_Vec3 rotate_vec( 0.0f ,0.0f, 0.0f );
- if( key_pressed( SettingsKeys::key_turn_left , KeyCode::Left ) ) rotate_vec.z+= +1.0f;
- if( key_pressed( SettingsKeys::key_turn_right, KeyCode::Right ) ) rotate_vec.z+= -1.0f;
- if( key_pressed( SettingsKeys::key_look_up , KeyCode::Up ) ) rotate_vec.x+= +1.0f;
- if( key_pressed( SettingsKeys::key_look_down , KeyCode::Down ) ) rotate_vec.x+= -1.0f;
+
+ if( key_pressed( SettingsKeys::key_perm_mlook, KeyCode::E ) ) mouse_look_ = !mouse_look_;
+ if( key_pressed( SettingsKeys::key_turn_left , KeyCode::KP4 ) ) rotate_vec.z+= +1.0f;
+ if( key_pressed( SettingsKeys::key_turn_right , KeyCode::KP6 ) ) rotate_vec.z+= -1.0f;
+ if( key_pressed( SettingsKeys::key_look_up , KeyCode::KP8 ) ) rotate_vec.x+= +1.0f;
+ if( key_pressed( SettingsKeys::key_look_down , KeyCode::KP2 ) ) rotate_vec.x+= -1.0f;
+ if( key_pressed( SettingsKeys::key_center_view, KeyCode::KP5 ) ) angle_.x = 0.0f;
const float rot_speed= 1.75f;
angle_+= dt_s * rot_speed * rotate_vec;
-
+
ClipCameraAngles();
- jump_pressed_= key_pressed( SettingsKeys::key_jump, KeyCode::Space );
+ jump_pressed_= key_pressed( SettingsKeys::key_jump, KeyCode::Mouse2 );
+ mouse_look_pressed_ = key_pressed( SettingsKeys::key_temp_mlook, KeyCode::Q );
}
void MovementController::SetSpeed( const float speed )
@@ -101,12 +85,12 @@ void MovementController::SetAngles( float z_angle, float x_angle )
}
void MovementController::GetAcceleration(
- const KeyboardState& keyboard_state,
+ const InputState& input_state,
float& out_dir, float& out_acceleration ) const
{
m_Vec3 move_vector(0.0f,0.0f,0.0f);
- const KeyChecker key_pressed( settings_,keyboard_state );
+ const KeyChecker key_pressed( settings_,input_state );
if( key_pressed( SettingsKeys::key_forward , KeyCode::W ) ) move_vector.y+= +1.0f;
if( key_pressed( SettingsKeys::key_backward , KeyCode::S ) ) move_vector.y+= -1.0f;
@@ -265,41 +249,44 @@ bool MovementController::JumpPressed() const
void MovementController::ControllerRotate( const int delta_x, const int delta_z )
{
- float base_sensetivity= settings_.GetOrSetFloat( SettingsKeys::mouse_sensetivity, 0.5f );
- base_sensetivity= std::max( 0.0f, std::min( base_sensetivity, 1.0f ) );
- settings_.SetSetting( SettingsKeys::mouse_sensetivity, base_sensetivity );
-
- float d_x_f, d_z_f;
- if( settings_.GetOrSetBool( "cl_mouse_filter", true ) )
+ if( ( mouse_look_pressed_ && !mouse_look_ ) || ( !mouse_look_pressed_ && mouse_look_) )
{
- d_x_f= float( delta_x + prev_controller_delta_x_ ) * 0.5f;
- d_z_f= float( delta_z + prev_controller_delta_z_ ) * 0.5f;
+ float base_sensetivity= settings_.GetOrSetFloat( SettingsKeys::mouse_sensetivity, 0.5f );
+ base_sensetivity= std::max( 0.0f, std::min( base_sensetivity, 1.0f ) );
+ settings_.SetSetting( SettingsKeys::mouse_sensetivity, base_sensetivity );
+
+ float d_x_f, d_z_f;
+ if( settings_.GetOrSetBool( "cl_mouse_filter", true ) )
+ {
+ d_x_f= float( delta_x + prev_controller_delta_x_ ) * 0.5f;
+ d_z_f= float( delta_z + prev_controller_delta_z_ ) * 0.5f;
+ }
+ else
+ {
+ d_x_f= float(delta_x);
+ d_z_f= float(delta_z);
+ }
+
+ if( settings_.GetOrSetBool( "cl_mouse_acceleration", true ) )
+ {
+ const float c_acceleration= 0.25f;
+ const float c_max_acceleration_factor= 2.0f;
+ d_x_f= std::min( c_acceleration * std::sqrt(std::abs(d_x_f)), c_max_acceleration_factor ) * d_x_f;
+ d_z_f= std::min( c_acceleration * std::sqrt(std::abs(d_z_f)), c_max_acceleration_factor ) * d_z_f;
+ }
+
+ const float c_max_exp_sensetivity= 8.0f;
+ const float exp_sensetivity= std::exp( base_sensetivity * std::log(c_max_exp_sensetivity) ); // [ 1; c_max_exp_sensetivity ]
+
+ const float c_pix_scale= 1.0f / 1024.0f;
+ const float z_direction= settings_.GetOrSetBool( SettingsKeys::reverse_mouse ) ? -1.0f : +1.0f;
+
+ angle_.x-= exp_sensetivity * c_pix_scale * d_x_f * z_direction;
+ angle_.z-= exp_sensetivity * c_pix_scale * d_z_f * 0.5f;
+
+ prev_controller_delta_x_= delta_x;
+ prev_controller_delta_z_= delta_z;
}
- else
- {
- d_x_f= float(delta_x);
- d_z_f= float(delta_z);
- }
-
- if( settings_.GetOrSetBool( "cl_mouse_acceleration", true ) )
- {
- const float c_acceleration= 0.25f;
- const float c_max_acceleration_factor= 2.0f;
- d_x_f= std::min( c_acceleration * std::sqrt(std::abs(d_x_f)), c_max_acceleration_factor ) * d_x_f;
- d_z_f= std::min( c_acceleration * std::sqrt(std::abs(d_z_f)), c_max_acceleration_factor ) * d_z_f;
- }
-
- const float c_max_exp_sensetivity= 8.0f;
- const float exp_sensetivity= std::exp( base_sensetivity * std::log(c_max_exp_sensetivity) ); // [ 1; c_max_exp_sensetivity ]
-
- const float c_pix_scale= 1.0f / 1024.0f;
- const float z_direction= settings_.GetOrSetBool( SettingsKeys::reverse_mouse ) ? -1.0f : +1.0f;
-
- angle_.x-= exp_sensetivity * c_pix_scale * d_x_f * z_direction;
- angle_.z-= exp_sensetivity * c_pix_scale * d_z_f * 0.5f;
-
- prev_controller_delta_x_= delta_x;
- prev_controller_delta_z_= delta_z;
}
void MovementController::ClipCameraAngles()
diff --git a/src/client/movement_controller.hpp b/src/client/movement_controller.hpp
index c0e416f8..8da908de 100644
--- a/src/client/movement_controller.hpp
+++ b/src/client/movement_controller.hpp
@@ -25,12 +25,12 @@ class MovementController final
void SetAspect( float aspect );
void UpdateParams();
- void Tick( const KeyboardState& keyboard_state );
+ void Tick( const InputState& input_state_ );
void SetSpeed( float speed );
void SetAngles( float z_angle, float x_angle );
void GetAcceleration(
- const KeyboardState& keyboard_state,
+ const InputState& input_state,
float& out_dir, float& out_acceleration ) const;
float GetEyeZShift() const;
@@ -65,7 +65,8 @@ class MovementController final
float speed_;
bool jump_pressed_;
-
+ bool mouse_look_pressed_;
+ bool mouse_look_;
const Time start_tick_;
Time prev_calc_tick_;
diff --git a/src/common/files.cpp b/src/common/files.cpp
index 10fba17b..7e3b2890 100644
--- a/src/common/files.cpp
+++ b/src/common/files.cpp
@@ -1,5 +1,4 @@
#include "files.hpp"
-
namespace ChasmReverse
{
@@ -31,5 +30,5 @@ void FileWrite( std::FILE* const file, const void* buffer, const unsigned int si
} while( write_total < size );
}
-
} // namespace ChasmReverse
+
diff --git a/src/common/files.hpp b/src/common/files.hpp
index 5494bd3e..127e7ccc 100644
--- a/src/common/files.hpp
+++ b/src/common/files.hpp
@@ -1,5 +1,7 @@
#pragma once
+#include "str.hpp"
#include
+#include
namespace ChasmReverse
{
@@ -8,3 +10,4 @@ void FileRead( std::FILE* const file, void* buffer, const unsigned int size );
void FileWrite( std::FILE* const file, const void* buffer, const unsigned int size );
} // namespace ChasmReverse
+
diff --git a/src/common/palette.cpp b/src/common/palette.cpp
index 41b561d5..15d47dc0 100644
--- a/src/common/palette.cpp
+++ b/src/common/palette.cpp
@@ -18,7 +18,7 @@ void LoadPalette( Palette& out_palette )
std::cout << "Could not read file \"" << palette_file_name << "\"" << std::endl;
return;
}
-
+ std::cout << "FOOO" << std::endl;
FileRead( file, out_palette.data(), out_palette.size() );
std::fclose( file );
diff --git a/src/common/str.cpp b/src/common/str.cpp
new file mode 100644
index 00000000..541ea296
--- /dev/null
+++ b/src/common/str.cpp
@@ -0,0 +1,55 @@
+#include "str.hpp"
+
+const char* ExtractExtension( const char* const file_path )
+{
+ unsigned int pos= std::strlen( file_path );
+
+ while( pos > 0u && file_path[ pos ] != '.' )
+ pos--;
+
+ return file_path + pos + 1u;
+}
+
+std::filesystem::path remove_extension( const std::filesystem::path& path )
+{
+ if( path == "." || path == ".." ) return path;
+
+ std::filesystem::path dst = path.stem();
+
+ while(!dst.extension().empty()) dst = dst.stem();
+
+ return path.parent_path() / dst;
+}
+
+const char* ExtractFileName( const char* const file_path )
+{
+ const char* file_name_pos= file_path;
+
+ const char* str= file_path;
+ while( *str != '\0' )
+ {
+ if( *str == '/' || *str == '\\' )
+ file_name_pos= str + 1u;
+
+ str++;
+ }
+
+ return file_name_pos;
+}
+
+std::string ToUpper( const std::string& s )
+{
+ std::string r= s;
+ for( char& c : r )
+ c= isalnum(c) ? std::toupper(c) : c;
+ return r;
+}
+
+std::string ToLower( const std::string& s )
+{
+ std::string r= s;
+ for( char& c : r )
+ c= isalnum(c) ? std::tolower(c) : c;
+ return r;
+}
+
diff --git a/src/common/str.hpp b/src/common/str.hpp
new file mode 100644
index 00000000..1ea6d7bd
--- /dev/null
+++ b/src/common/str.hpp
@@ -0,0 +1,57 @@
+#pragma once
+
+#ifdef _MSC_VER
+#define strcasecmp _stricmp
+#define strncasecmp _strnicmp
+#ifdef _stristr
+#define strcasestr _stristr
+#define strncasestr _strnistr
+#else
+#include
+inline char* strcasestr(const char* haystack, const char* needle)
+{
+ return StrStrI(needle, haystack);
+}
+
+inline char* strncasestr(const char* haystack, const char* needle, size_t n)
+{
+ return StrStrNI(needle, haystack, n);
+}
+
+#endif
+#else
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE
+#endif
+#endif
+#include
+#include
+#include
+
+#define GetSubstring strcasestr
+constexpr bool StringEquals( const char* s0, const char* s1, size_t n = 0)
+{
+ return n == 0 ? ( ::strcasecmp( s0, s1 ) == 0 ) : ( ::strncasecmp( s0, s1, n ) == 0 );
+}
+
+const char* ExtractFileName( const char* const file_path );
+const char* ExtractExtension( const char* const file_path );
+std::filesystem::path remove_extension( const std::filesystem::path& path );
+std::string ToUpper( const std::string& s );
+std::string ToLower( const std::string& s );
+/*
+// Own replacement for nonstandard strncasecmp
+static bool StringEquals( const char* const s0, const char* const s1, const unsigned int max_length )
+{
+ unsigned int i= 0;
+ while( s0[i] != '\0' && s1[i] != '\0' && i < max_length )
+ {
+ if( std::tolower( s0[i] ) != std::tolower( s1[i] ) )
+ return false;
+ i++;
+ }
+
+ return i == max_length || s0[i] == s1[i];
+}
+*/
+
diff --git a/src/common/tga.cpp b/src/common/tga.cpp
index be95393e..8ba7ee2a 100644
--- a/src/common/tga.cpp
+++ b/src/common/tga.cpp
@@ -3,6 +3,10 @@
#include "files.hpp"
#include "tga.hpp"
+#include
+#include
+#include
+#include
namespace ChasmReverse
{
@@ -39,11 +43,11 @@ void WriteTGA(
TGAHeader tga;
tga.id_length= 0;
- tga.colormap_type= 1; // image with colormap
- tga.image_type= 1; // image with palette without compression
+ tga.colormap_type= (palette == nullptr) ? 0 : 1; // image with/out colormap
+ tga.image_type= (palette == nullptr) ? 2 : 1; // image with/out palette without compression
tga.colormap_index= 0;
- tga.colormap_length= 256;
+ tga.colormap_length= (palette == nullptr) ? 0 : 256;
tga.colormap_size= 32;
tga.x_origin= 0;
@@ -51,8 +55,8 @@ void WriteTGA(
tga.width = width ;
tga.height= height;
- tga.pixel_bits= 8;
- tga.attributes= 1 << 5; // vertical flip flag
+ tga.pixel_bits= (palette == nullptr) ? 32 : 8;
+ tga.attributes= (palette == nullptr) ? 0 : 1 << 5; // vertical flip flag
tga.attributes|= 8; // bits in alpha-channell
std::FILE* file= std::fopen( file_name, "wb" );
@@ -64,20 +68,30 @@ void WriteTGA(
FileWrite( file, &tga, sizeof(tga) );
- unsigned char palette_rb_swapped[ 256 * 4 ];
- for( unsigned int i= 0; i < 256; i++ )
+ if(palette != nullptr)
{
- palette_rb_swapped[ i * 4 + 0 ]= palette[ i * 3 + 2 ];
- palette_rb_swapped[ i * 4 + 1 ]= palette[ i * 3 + 1 ];
- palette_rb_swapped[ i * 4 + 2 ]= palette[ i * 3 + 0 ];
- palette_rb_swapped[ i * 4 + 3 ]= 255;
+ unsigned char palette_rb_swapped[ 256 * 4 ];
+ for( unsigned int i= 0; i < 256; i++ )
+ {
+ palette_rb_swapped[ i * 4 + 0 ]= palette[ i * 3 + 2 ];
+ palette_rb_swapped[ i * 4 + 1 ]= palette[ i * 3 + 1 ];
+ palette_rb_swapped[ i * 4 + 2 ]= palette[ i * 3 + 0 ];
+ palette_rb_swapped[ i * 4 + 3 ]= 255;
+ }
+ palette_rb_swapped[255 * 4 + 3]= 0;
+
+
+ FileWrite( file, palette_rb_swapped, sizeof(palette_rb_swapped) );
+ FileWrite( file, data, width * height );
+ }
+ else
+ {
+ std::vector> pixels(width * height);
+ size_t i = 0;
+ for(auto & p : pixels)
+ p = { data[ i * 4 + 2 ], data[ i * 4 + 1 ], data[ i * 4 + 0 ], data[ i++ * 4 + 3 ] };
+ FileWrite( file, &pixels.front().front(), pixels.size() * pixels.front().size() * sizeof(pixels.front().front()) );
}
- palette_rb_swapped[255 * 4 + 3]= 0;
-
-
- FileWrite( file, palette_rb_swapped, sizeof(palette_rb_swapped) );
- FileWrite( file, data, width * height );
-
std::fclose( file );
}
diff --git a/src/game_resources.cpp b/src/game_resources.cpp
index 17f6861b..0b1d4f21 100644
--- a/src/game_resources.cpp
+++ b/src/game_resources.cpp
@@ -421,7 +421,6 @@ static void LoadSoundsDescriptionFromFileData(
continue;
GameResources::SoundDescription& sound= out_sounds[ sound_number ];
-
char* file_name_dst= sound.file_name;
while( !std::isspace( *s ) && *s != '\0' )
{
@@ -431,6 +430,8 @@ static void LoadSoundsDescriptionFromFileData(
}
*file_name_dst= '\0';
+ std::replace(sound.file_name, file_name_dst, '\\', '/');
+
while( std::isspace( *s ) && *s != '\0' ) s++;
if( *s == 'v' || *s == 'V' )
{
@@ -471,16 +472,15 @@ static void LoadItemsModels(
for( unsigned int i= 0u; i < game_resources.items_models.size(); i++ )
{
const GameResources::ItemDescription& item_description= game_resources.items_description[i];
+ const std::filesystem::path model_file_name(item_description.model_file_name);
+ const std::filesystem::path animation_file_name(item_description.animation_file_name);
- char model_file_path[ GameResources::c_max_file_path_size ]= "MODELS/";
- char animation_file_path[ GameResources::c_max_file_path_size ]= "ANI/";
-
- std::strcat( model_file_path, item_description.model_file_name );
- std::strcat( animation_file_path, item_description.animation_file_name );
+ std::filesystem::path model_file_path( "MODELS/" / model_file_name);
+ std::filesystem::path animation_file_path( "MODELS/" / animation_file_name);
vfs.ReadFile( model_file_path, file_content );
- if( item_description.animation_file_name[0u] != '\0' )
+ if( !animation_file_name.empty() )
vfs.ReadFile( animation_file_path, animation_file_content );
else
animation_file_content.clear();
@@ -500,9 +500,9 @@ static void LoadMonstersModels(
for( unsigned int i= 0u; i < game_resources.monsters_models.size(); i++ )
{
const GameResources::MonsterDescription& monster_description= game_resources.monsters_description[i];
+ const std::filesystem::path model_file_name( monster_description.model_file_name );
- char model_file_path[ GameResources::c_max_file_path_size ]= "CARACTER/";
- std::strcat( model_file_path, monster_description.model_file_name );
+ std::filesystem::path model_file_path( "CARACTER/" / model_file_name );
vfs.ReadFile( model_file_path, file_content );
LoadModel_car( file_content, game_resources.monsters_models[i] );
@@ -519,7 +519,9 @@ static void LoadEffectsSprites(
for( unsigned int i= 0u; i < game_resources.effects_sprites.size(); i++ )
{
- vfs.ReadFile( game_resources.sprites_effects_description[i].sprite_file_name, file_content );
+ const std::filesystem::path sprite_file_name( game_resources.sprites_effects_description[i].sprite_file_name );
+
+ vfs.ReadFile( sprite_file_name, file_content );
LoadObjSprite( file_content, game_resources.effects_sprites[i] );
}
}
@@ -529,12 +531,12 @@ static void LoadBMPObjectsSprites(
GameResources& game_resources )
{
game_resources.bmp_objects_sprites.resize( game_resources.bmp_objects_description.size() );
-
Vfs::FileContent file_content;
for( unsigned int i= 0u; i < game_resources.bmp_objects_sprites.size(); i++ )
{
- vfs.ReadFile( game_resources.bmp_objects_description[i].sprite_file_name, file_content );
+ const std::filesystem::path sprite_file_name( game_resources.bmp_objects_description[i].sprite_file_name );
+ vfs.ReadFile( sprite_file_name, file_content );
LoadObjSprite( file_content, game_resources.bmp_objects_sprites[i] );
}
}
@@ -552,14 +554,13 @@ static void LoadWeaponsModels(
for( unsigned int i= 0u; i < game_resources.weapons_models.size(); i++ )
{
const GameResources::WeaponDescription& weapon_description= game_resources.weapons_description[i];
+ const std::filesystem::path model_file_name( weapon_description.model_file_name );
+ const std::filesystem::path animation_file_name( weapon_description.animation_file_name );
+ const std::filesystem::path reloading_animation_file_name( weapon_description.reloading_animation_file_name );
- char model_file_path[ GameResources::c_max_file_path_size ]= "MODELS/";
- char animation_file_path[ GameResources::c_max_file_path_size ]= "ANI/WEAPON/";
- char reloading_animation_file_path[ GameResources::c_max_file_path_size ]= "ANI/WEAPON/";
-
- std::strcat( model_file_path, weapon_description.model_file_name );
- std::strcat( animation_file_path, weapon_description.animation_file_name );
- std::strcat( reloading_animation_file_path, weapon_description.reloading_animation_file_name );
+ std::filesystem::path model_file_path( "MODELS/" / model_file_name );
+ std::filesystem::path animation_file_path( "ANI/WEAPON/" / animation_file_name );
+ std::filesystem::path reloading_animation_file_path( "ANI/WEAPON/" / reloading_animation_file_name );
vfs.ReadFile( model_file_path, file_content );
vfs.ReadFile( animation_file_path, animation_file_content[0] );
@@ -581,18 +582,21 @@ static void LoadRocketsModels(
for( unsigned int i= 0u; i < game_resources.rockets_models.size(); i++ )
{
const GameResources::RocketDescription& rocket_description= game_resources.rockets_description[i];
+ const std::filesystem::path model_file_name( rocket_description.model_file_name );
+ const std::filesystem::path animation_file_name( rocket_description.animation_file_name );
- if( rocket_description.model_file_name[0] == '\0' )
+ if( model_file_name.empty() )
continue;
- char model_file_path[ GameResources::c_max_file_path_size ]= "MODELS/";
- char animation_file_path[ GameResources::c_max_file_path_size ]= "ANI/";
+ std::filesystem::path model_file_path( "MODELS/" / model_file_name );
+ std::filesystem::path animation_file_path( "ANI/" / animation_file_name );
- std::strcat( model_file_path, rocket_description.model_file_name );
- std::strcat( animation_file_path, rocket_description.animation_file_name );
vfs.ReadFile( model_file_path, file_content );
- vfs.ReadFile( animation_file_path, animation_file_content );
+ if( !animation_file_name.empty() )
+ vfs.ReadFile( animation_file_path, animation_file_content );
+ else
+ animation_file_content.clear();
LoadModel_o3( file_content, animation_file_content, game_resources.rockets_models[i] );
}
@@ -609,12 +613,11 @@ static void LoadGibsModels(
for( unsigned int i= 0u; i < game_resources.gibs_models.size(); i++ )
{
const GameResources::GibDescription& gib_description= game_resources.gibs_description[i];
-
+ const std::filesystem::path model_file_name= gib_description.model_file_name;
if( gib_description.model_file_name[0] == '\0' )
continue;
- char model_file_path[ GameResources::c_max_file_path_size ]= "MODELS/";
- std::strcat( model_file_path, gib_description.model_file_name );
+ std::filesystem::path model_file_path( "MODELS/" / model_file_name );
vfs.ReadFile( model_file_path, model_file_content );
LoadModel_o3( model_file_content, Vfs::FileContent(), game_resources.gibs_models[i] );
diff --git a/src/host.cpp b/src/host.cpp
index 15e3f410..7a747d6b 100644
--- a/src/host.cpp
+++ b/src/host.cpp
@@ -1,5 +1,3 @@
-#include
-
#include
#include
#include
@@ -15,7 +13,8 @@
#include "shared_drawers.hpp"
#include "save_load.hpp"
#include "sound/sound_engine.hpp"
-
+#include "key_checker.hpp"
+#include "shared_settings_keys.hpp"
#include "host.hpp"
namespace PanzerChasm
@@ -89,34 +88,31 @@ Host::Host( const int argc, const char* const* const argv )
base_window_title_= "PanzerChasm";
{
- Log::Info( "Read game archive" );
+ Log::Info( "Initializing data files...\n" );
+ std::filesystem::path csm_file = "CSM.BIN";
+ std::filesystem::path addon_path;
+ if( program_arguments_.GetParamValue( "csm" ) != nullptr )
+ csm_file= program_arguments_.GetParamValue( "csm" );
- const char* csm_file= "CSM.BIN";
- if( const char* const overrided_csm_file = program_arguments_.GetParamValue( "csm" ) )
- {
- csm_file= overrided_csm_file;
- Log::Info( "Trying to load CSM file: \"", overrided_csm_file, "\"" );
- }
+ if( program_arguments_.GetParamValue( "addon" ) != nullptr )
+ addon_path= program_arguments_.GetParamValue( "addon" );
- const char* const addon_path= program_arguments_.GetParamValue( "addon" );
- if( addon_path != nullptr )
+ if( !addon_path.empty() )
{
base_window_title_+= " - ";
base_window_title_+= addon_path;
-
- Log::Info( "Trying to load addon \"", addon_path, "\"" );
}
vfs_= std::make_shared( csm_file, addon_path );
+ Log::Info("");
}
// Create directory for saves.
// TODO - allow user change this directory, using command-line argument or settings, for example.
CreateSlotSavesDir();
-
Log::Info( "Loading game resources" );
game_resources_= LoadGameResources( vfs_ );
-
+ Log::Info( "" );
VidRestart();
Log::Info( "Initialize console" );
@@ -155,7 +151,6 @@ Host::~Host()
bool Host::Loop()
{
const Time tick_start_time= Time::CurrentTime();
-
// Events processing
InputState input_state;
if( system_window_ != nullptr )
@@ -164,7 +159,6 @@ bool Host::Loop()
system_window_->GetInput( events_ );
system_window_->GetInputState( input_state );
}
-
// Special host key events.
for( const SystemEvent& event : events_ )
{
@@ -223,6 +217,7 @@ bool Host::Loop()
client_->Loop( input_state, really_paused );
}
+
// Draw operations
if( system_window_ && !system_window_->IsMinimized() )
{
@@ -262,6 +257,11 @@ bool Host::Loop()
}
system_window_->EndFrame();
+
+ const KeyChecker key_pressed( settings_, input_state );
+
+ if( !input_goes_to_menu && key_pressed( SettingsKeys::key_screenshot, KeyCode::F12 ) )
+ system_window_->CaptureScreen();
}
@@ -398,28 +398,20 @@ void Host::GetSavesNames( SavesNames& out_saves_names )
{
SaveComment& out_save_comment= out_saves_names[slot];
- char file_name[32];
- GetSaveFileNameForSlot( slot, file_name, sizeof(file_name) );
-
- if( LoadSaveComment( file_name, out_save_comment ) )
- { /* all ok */ }
- else
+ std::filesystem::path file_name = GetSaveFileNameForSlot( slot );
+ if( !LoadSaveComment( file_name, out_save_comment ) )
out_save_comment[0]= '\0';
}
}
-void Host::SaveGame( const unsigned int slot_number )
+void Host::SaveGame( const uint8_t slot_number )
{
- char file_name[64];
- GetSaveFileNameForSlot( slot_number, file_name, sizeof(file_name) );
- DoSave( file_name );
+ DoSave( GetSaveFileNameForSlot( slot_number ) );
}
-void Host::LoadGame( const unsigned int slot_number )
+void Host::LoadGame( const uint8_t slot_number )
{
- char file_name[64];
- GetSaveFileNameForSlot( slot_number, file_name, sizeof(file_name) );
- DoLoad( file_name );
+ DoLoad( GetSaveFileNameForSlot( slot_number ) );
}
void Host::VidRestart()
@@ -606,7 +598,7 @@ void Host::DoRunLevel( const unsigned int map_number, const DifficultyType diffi
is_single_player_= true;
}
-void Host::DoSave( const char* const save_file_name )
+void Host::DoSave( const std::filesystem::path& save_file_name )
{
if( !( local_server_ != nullptr && client_ != nullptr && is_single_player_ ) )
{
@@ -628,7 +620,7 @@ void Host::DoSave( const char* const save_file_name )
Log::User( "Game save failed." );
}
-void Host::DoLoad( const char* const save_file_name )
+void Host::DoLoad( const std::filesystem::path& save_file_name )
{
SaveLoadBuffer save_buffer;
unsigned int save_buffer_pos= 0u;
diff --git a/src/host.hpp b/src/host.hpp
index 9aa64fda..5dc856b0 100644
--- a/src/host.hpp
+++ b/src/host.hpp
@@ -50,8 +50,8 @@ class Host final : public HostCommands
virtual void GetSavesNames( SavesNames& out_saves_names ) override;
virtual bool SaveAvailable() const override;
- virtual void SaveGame( unsigned int slot_number ) override;
- virtual void LoadGame( unsigned int slot_number ) override;
+ virtual void SaveGame( uint8_t slot_number ) override;
+ virtual void LoadGame( uint8_t slot_number ) override;
virtual void VidRestart() override;
@@ -70,8 +70,8 @@ class Host final : public HostCommands
void DoVidRestart();
void DoRunLevel( unsigned int map_number, DifficultyType difficulty );
- void DoSave( const char* save_file_name );
- void DoLoad( const char* save_file_name );
+ void DoSave( const std::filesystem::path& save_file_name );
+ void DoLoad( const std::filesystem::path& save_file_name );
void DrawLoadingFrame( float progress, const char* caption );
diff --git a/src/host_commands.hpp b/src/host_commands.hpp
index fa20a023..53883858 100644
--- a/src/host_commands.hpp
+++ b/src/host_commands.hpp
@@ -38,8 +38,8 @@ class HostCommands
virtual void GetSavesNames( SavesNames& out_saves_names )= 0;
virtual bool SaveAvailable() const = 0;
- virtual void SaveGame( unsigned int slot_number )= 0;
- virtual void LoadGame( unsigned int slot_number )= 0;
+ virtual void SaveGame( uint8_t slot_number )= 0;
+ virtual void LoadGame( uint8_t slot_number )= 0;
virtual void VidRestart()= 0;
};
diff --git a/src/key_checker.cpp b/src/key_checker.cpp
new file mode 100644
index 00000000..9d61c30a
--- /dev/null
+++ b/src/key_checker.cpp
@@ -0,0 +1,46 @@
+#include "key_checker.hpp"
+
+namespace PanzerChasm
+{
+ KeyChecker::KeyChecker( Settings& settings, const InputState& input_state )
+ : settings_(settings), input_state_(input_state), event_(SystemEvent()), event_source_(false)
+ {}
+ KeyChecker::KeyChecker( Settings& settings, const SystemEvent& event )
+ : settings_(settings), input_state_(InputState()), event_(event), event_source_(true)
+ {}
+
+ bool KeyChecker::operator()( const char* const key_setting_name, const KeyCode default_value ) const
+ {
+ const char32_t key = std::clamp((const char32_t)settings_.GetOrSetInt( key_setting_name, static_cast(default_value)), (const char32_t)KeyCode::Unknown, (const char32_t)KeyCode::KeyCount);
+
+ if(event_source_)
+ {
+ char32_t key_event = KeyCode::Unknown;
+ switch(event_.type)
+ {
+ case SystemEvent::Type::Wheel:
+ if( event_.event.wheel.delta > 0 ) key_event = KeyCode::MouseWheelUp;
+ if( event_.event.wheel.delta < 0 ) key_event = KeyCode::MouseWheelDown;
+ break;
+ case SystemEvent::Type::MouseKey:
+ if( event_.event.mouse_key.pressed )
+ key_event = KeyCode::MouseUnknown + (const char32_t)event_.event.mouse_key.mouse_button;
+ break;
+ case SystemEvent::Type::Key:
+ if( event_.event.key.pressed ) key_event = event_.event.key.key_code;
+ break;
+ default:
+ break;
+ }
+ if( key == std::clamp( key_event, (char32_t)KeyCode::Unknown, (char32_t)KeyCode::KeyCount ) ) return true;
+ }
+ else
+ {
+ if( key < KeyCode::MouseUnknown)
+ return input_state_.keyboard[ key ];
+ else
+ return input_state_.mouse[ key - KeyCode::MouseUnknown ];
+ }
+ return false;
+ }
+} // namespace PanzerChasm
diff --git a/src/key_checker.hpp b/src/key_checker.hpp
new file mode 100644
index 00000000..276ba6bd
--- /dev/null
+++ b/src/key_checker.hpp
@@ -0,0 +1,25 @@
+#pragma once
+#include
+#include "settings.hpp"
+#include "math_utils.hpp"
+#include "system_event.hpp"
+
+namespace PanzerChasm
+{
+
+using KeyCode= SystemEvent::KeyEvent::KeyCode;
+
+class KeyChecker
+{
+public:
+ KeyChecker( Settings& settings, const InputState& input_state );
+ KeyChecker( Settings& settings, const SystemEvent& event );
+ bool operator()( const char* const key_setting_name, const KeyCode default_value ) const;
+private:
+ Settings& settings_;
+ bool event_source_;
+ const InputState& input_state_;
+ const SystemEvent& event_;
+};
+
+} // namespace PanzerChasm
diff --git a/src/map_loader.cpp b/src/map_loader.cpp
index f39e1b7a..d96786b8 100644
--- a/src/map_loader.cpp
+++ b/src/map_loader.cpp
@@ -4,7 +4,7 @@
#include "assert.hpp"
#include "log.hpp"
#include "math_utils.hpp"
-
+#include "common/files.hpp"
#include "map_loader.hpp"
namespace PanzerChasm
@@ -60,41 +60,6 @@ SIZE_ASSERT( MapMonster, 8 );
namespace
{
-// Case-unsensitive strings equality-comparision
-static bool StringEquals( const char* const s0, const char* const s1 )
-{
- unsigned int i= 0;
- while( s0[i] != '\0' && s1[i] != '\0' )
- {
- if( std::tolower( s0[i] ) != std::tolower( s1[i] ) )
- return false;
- i++;
- }
-
- return std::tolower( s0[i] ) == std::tolower( s1[i] );
-}
-
-// Case-unsensitive substring search
-static const char* GetSubstring( const char* const search_where, const char* const search_what )
-{
- const char* str= search_where;
- while( *str != '\0' )
- {
- unsigned int i= 0u;
-
- while( str[i] != '\0' && search_what[i] != '\0' &&
- std::tolower( str[i] ) == std::tolower( search_what [i] ) )
- i++;
-
- if( search_what [i] == '\0' )
- return str;
-
- str++;
- }
-
- return nullptr;
-}
-
static decltype(MapData::Link::type) LinkTypeFromString( const char* const str )
{
if( StringEquals( str, "link" ) )
diff --git a/src/math_utils.hpp b/src/math_utils.hpp
index 91ee4cd7..b6387a0d 100644
--- a/src/math_utils.hpp
+++ b/src/math_utils.hpp
@@ -36,3 +36,15 @@ float DistanceToLineSegment(
const m_Vec2& v1 );
} // namespace PanzerChasm
+
+
+#if __cplusplus < 201703L
+namespace std
+{
+ template
+ constexpr inline Tp_ clamp(Tp_ dst, Tp_ lo, Tp_ hi)
+ {
+ return (dst <= hi ? (dst >= lo ? dst : lo) : hi);
+ }
+}
+#endif
diff --git a/src/menu.cpp b/src/menu.cpp
index ac5f1c77..8d121e1c 100644
--- a/src/menu.cpp
+++ b/src/menu.cpp
@@ -11,8 +11,8 @@
#include "sound/sound_engine.hpp"
#include "sound/sound_id.hpp"
#include "system_window.hpp"
-
#include "menu.hpp"
+#include "key_checker.hpp"
namespace PanzerChasm
{
@@ -63,6 +63,7 @@ class MenuBase
virtual MenuBase* Select() { return this; }
virtual MenuBase* Back() { return this; }
virtual MenuBase* CharInput( char32_t ch ) { return this; }
+ bool getInSetMode() { return in_set_mode_; }
protected:
bool in_set_mode_ = false;
private:
@@ -75,16 +76,30 @@ MenuBase* MenuBase::ProcessEvent( const SystemEvent& event )
switch(event.type)
{
case SystemEvent::Type::Wheel:
- if(event.event.wheel.delta != 0)
- if(event.event.wheel.delta > 0) Up(); else Down();
+ if( in_set_mode_ )
+ {
+ CharInput( static_cast( event.event.wheel.delta > 0 ? KeyCode::MouseWheelUp : KeyCode::MouseWheelDown ) ); break;
+ }
+ else
+ {
+ if(event.event.wheel.delta != 0)
+ if(event.event.wheel.delta > 0) Up(); else Down();
+ }
break;
case SystemEvent::Type::MouseKey:
if( event.event.mouse_key.pressed )
{
- switch(event.event.mouse_key.mouse_button)
+ if ( in_set_mode_ )
{
- case SystemEvent::MouseKeyEvent::Button::Left: return Select(); break;
- case SystemEvent::MouseKeyEvent::Button::Right: return Back(); break;
+ CharInput( (const char32_t)KeyCode::MouseUnknown + (const char32_t)event.event.mouse_key.mouse_button); break;
+ }
+ else
+ {
+ switch(event.event.mouse_key.mouse_button)
+ {
+ case SystemEvent::MouseKeyEvent::Button::Left: return Select(); break;
+ case SystemEvent::MouseKeyEvent::Button::Right: return Back(); break;
+ }
}
}
break;
@@ -109,6 +124,7 @@ MenuBase* MenuBase::ProcessEvent( const SystemEvent& event )
}
}
}
+ break;
case SystemEvent::Type::CharInput: if(!in_set_mode_) CharInput( static_cast(event.event.char_input.ch) ); break;
break;
default: break;
@@ -677,7 +693,7 @@ PlayerSetupMenu::PlayerSetupMenu( MenuBase* parent, const Sound::SoundEnginePtr&
{
std::strncpy(
nick_name_,
- settings_.GetOrSetString( SettingsKeys::player_name, "n00b" ),
+ settings_.GetOrSetString( SettingsKeys::player_name, "Player" ),
sizeof(nick_name_) );
color_= settings_.GetOrSetInt( SettingsKeys::player_color, 0 );
@@ -1021,15 +1037,26 @@ class ControlsMenu final : public MenuBase
const ControlsMenu::KeySettings ControlsMenu::c_key_settings[]=
{
- { "Move Forward" , SettingsKeys::key_forward , KeyCode::W },
- { "Move Backward" , SettingsKeys::key_backward , KeyCode::S },
- { "Strafe Left" , SettingsKeys::key_step_left , KeyCode::A },
- { "Strafe Right" , SettingsKeys::key_step_right , KeyCode::D },
- { "Turn Left" , SettingsKeys::key_turn_left , KeyCode::Left },
- { "Turn Right" , SettingsKeys::key_turn_right , KeyCode::Right },
- { "Look Up" , SettingsKeys::key_look_up , KeyCode::Up },
- { "Look Down" , SettingsKeys::key_look_down , KeyCode::Down },
- { "Jump" , SettingsKeys::key_jump , KeyCode::Space },
+ { "Move Forward" , SettingsKeys::key_forward , KeyCode::W },
+ { "Move Backward" , SettingsKeys::key_backward , KeyCode::S },
+ { "Strafe Left" , SettingsKeys::key_step_left , KeyCode::A },
+ { "Strafe Right" , SettingsKeys::key_step_right , KeyCode::D },
+ { "Speed Up" , SettingsKeys::key_speed_up , KeyCode::LeftShift },
+ { "Always Run" , SettingsKeys::key_always_run , KeyCode::CapsLock },
+ { "Jump" , SettingsKeys::key_jump , KeyCode::Mouse2 },
+ { "Fire" , SettingsKeys::key_fire , KeyCode::Mouse1 },
+ { "Change Weapon" , SettingsKeys::key_weapon_change , KeyCode::Mouse3 },
+ { "Next Weapon" , SettingsKeys::key_weapon_next , KeyCode::MouseWheelDown },
+ { "Prev Weapon" , SettingsKeys::key_weapon_prev , KeyCode::MouseWheelUp },
+ { "Strafe On" , SettingsKeys::key_strafe_on , KeyCode::RightShift },
+ { "Permanent MLook" , SettingsKeys::key_perm_mlook , KeyCode::E },
+ { "Temporary MLook" , SettingsKeys::key_temp_mlook , KeyCode::Q },
+ { "Turn Left" , SettingsKeys::key_turn_left , KeyCode::KP4 },
+ { "Turn Right" , SettingsKeys::key_turn_right , KeyCode::KP6 },
+ { "Look Up" , SettingsKeys::key_look_up , KeyCode::KP8 },
+ { "Center View" , SettingsKeys::key_center_view , KeyCode::KP5 },
+ { "Look Down" , SettingsKeys::key_look_down , KeyCode::KP2 },
+ { "Screenshot" , SettingsKeys::key_screenshot , KeyCode::F12 },
};
const unsigned int ControlsMenu::c_key_setting_count= sizeof(ControlsMenu::c_key_settings) / sizeof(ControlsMenu::c_key_settings[0]);
@@ -1111,7 +1138,14 @@ MenuBase* ControlsMenu::Select()
MenuBase* ControlsMenu::Back()
{
if( in_set_mode_ )
+ {
in_set_mode_= false;
+ }
+ else
+ {
+ settings_.SetSetting( c_key_settings[ current_row_ ].setting_name, static_cast(KeyCode::Unknown) );
+ PlayMenuSound( Sound::SoundId::MenuSelect );
+ }
return this;
}
@@ -1252,7 +1286,7 @@ void GraphicsMenu::Draw( IMenuDrawer& menu_drawer, ITextDrawer& text_draw )
if( current_renderer_ == Renderer::Software )
{
- char pixel_size[16];
+ char pixel_size[16] = "";
std::snprintf( pixel_size, sizeof(pixel_size), "%d", settings_.GetOrSetInt( SettingsKeys::software_scale, 1 ) );
text_draw.Print(
@@ -1365,8 +1399,8 @@ void GraphicsMenu::Left()
settings_.SetSetting( SettingsKeys::software_rendering, current_renderer_ == Renderer::Software );
PlayMenuSound( Sound::SoundId::MenuChange );
}
- int pixel_size;
- unsigned int msaa_level;
+ int pixel_size = 1;
+ unsigned int msaa_level = 0;
switch( current_renderer_ )
{
@@ -1375,7 +1409,8 @@ void GraphicsMenu::Left()
{
case RowSoftware::PixelSize:
pixel_size= settings_.GetInt( SettingsKeys::software_scale, 1 ) - 1;
- settings_.SetSetting( SettingsKeys::software_scale, std::max( 1, std::min( pixel_size, 5 ) ) );
+ pixel_size = pixel_size < 1 ? 4 : pixel_size;
+ settings_.SetSetting( SettingsKeys::software_scale, std::max(1, pixel_size % 5));
PlayMenuSound( Sound::SoundId::MenuScroll );
break;
case RowOpenGL::Shadows:
@@ -1416,11 +1451,8 @@ void GraphicsMenu::Left()
break;
case RowOpenGL::MSAA:
msaa_level = settings_.GetInt( SettingsKeys::opengl_msaa_level, 2 );
- if( msaa_level > 0 )
- {
- msaa_level--;
- PlayMenuSound( Sound::SoundId::MenuChange );
- }
+ msaa_level = ((msaa_level <= 0 ? 5 : msaa_level) - 1) % 5;
+ PlayMenuSound( Sound::SoundId::MenuChange );
settings_.SetSetting( SettingsKeys::opengl_msaa_level, int(msaa_level) );
break;
case RowOpenGL::Shadows:
@@ -1446,8 +1478,8 @@ void GraphicsMenu::Right()
PlayMenuSound( Sound::SoundId::MenuChange );
}
- int pixel_size;
- unsigned int msaa_level;
+ int pixel_size = 1;
+ unsigned int msaa_level = 0;
switch( current_renderer_ )
{
case Renderer::Software:
@@ -1455,7 +1487,7 @@ void GraphicsMenu::Right()
{
case RowSoftware::PixelSize:
pixel_size= settings_.GetInt( SettingsKeys::software_scale, 1 ) + 1;
- settings_.SetSetting( SettingsKeys::software_scale, std::max( 1, std::min( pixel_size, 5 ) ) );
+ settings_.SetSetting( SettingsKeys::software_scale, std::max(1, pixel_size % 5) );
PlayMenuSound( Sound::SoundId::MenuScroll );
break;
case RowOpenGL::Shadows:
@@ -1487,12 +1519,9 @@ void GraphicsMenu::Right()
PlayMenuSound( Sound::SoundId::MenuChange );
break;
case RowOpenGL::MSAA:
- msaa_level= settings_.GetInt( SettingsKeys::opengl_msaa_level, 2 );
- if( msaa_level < 4 )
- {
- msaa_level++;
- PlayMenuSound( Sound::SoundId::MenuChange );
- }
+ msaa_level = settings_.GetInt( SettingsKeys::opengl_msaa_level, 2 );
+ ++msaa_level %= 5;
+ PlayMenuSound( Sound::SoundId::MenuChange );
settings_.SetSetting( SettingsKeys::opengl_msaa_level, int(msaa_level) );
break;
case RowOpenGL::Shadows:
@@ -1519,11 +1548,18 @@ MenuBase* GraphicsMenu::Select()
PlayMenuSound( Sound::SoundId::MenuChange );
}
+ int pixel_size = 1;
+ unsigned int msaa_level = 0;
switch( current_renderer_ )
{
case Renderer::Software:
switch( current_row_)
{
+ case RowSoftware::PixelSize:
+ pixel_size= settings_.GetInt( SettingsKeys::software_scale, 1 ) + 1;
+ settings_.SetSetting( SettingsKeys::software_scale, std::max(1, pixel_size % 5));
+ PlayMenuSound( Sound::SoundId::MenuScroll );
+ break;
case RowSoftware::ApplyNow:
PlayMenuSound( Sound::SoundId::MenuChange );
host_commands_.VidRestart();
@@ -1556,6 +1592,12 @@ MenuBase* GraphicsMenu::Select()
PlayMenuSound( Sound::SoundId::MenuChange );
host_commands_.VidRestart();
break;
+ case RowOpenGL::MSAA:
+ msaa_level= settings_.GetInt( SettingsKeys::opengl_msaa_level, 2 );
+ ++msaa_level %= 5;
+ PlayMenuSound( Sound::SoundId::MenuChange );
+ settings_.SetSetting( SettingsKeys::opengl_msaa_level, int(msaa_level) );
+ break;
case RowOpenGL::Shadows:
settings_.SetSetting( SettingsKeys::shadows, !settings_.GetBool( SettingsKeys::shadows, true ) );
PlayMenuSound( Sound::SoundId::MenuChange );
@@ -1591,7 +1633,7 @@ class VideoMenu final : public MenuBase
{
enum : int
{
- Fullscreen= 0, Display, FullscreenResolution, Frequency, WindowWidth, WindowHeight, ApplyNow, NumRows
+ Fullscreen= 0, Display, FullscreenResolution, Frequency, VerticalSync, DrawFPS, WindowWidth, WindowHeight, ApplyNow, NumRows
};
};
@@ -1629,6 +1671,9 @@ VideoMenu::VideoMenu( MenuBase* parent, const Sound::SoundEnginePtr& sound_engin
const int width = settings_.GetInt( SettingsKeys::fullscreen_width );
const int height= settings_.GetInt( SettingsKeys::fullscreen_height );
const int frequency= settings_.GetInt( SettingsKeys::fullscreen_frequency );
+ settings_.GetOrSetBool( SettingsKeys::vsync, true );
+ settings_.GetOrSetBool( SettingsKeys::draw_fps, true );
+
if( !video_modes_.empty() )
{
const SystemWindow::VideoModes& display_modes= video_modes_[display_];
@@ -1730,6 +1775,29 @@ void VideoMenu::Draw( IMenuDrawer& menu_drawer, ITextDrawer& text_draw )
current_row_ == Row::Frequency ? ITextDrawer::FontColor::YellowGreen : ITextDrawer::FontColor::White,
ITextDrawer::Alignment::Right );
+ text_draw.Print(
+ param_descr_x, y + Row::VerticalSync * y_step,
+ "Vertical Sync", scale,
+ current_row_ == Row::VerticalSync ? ITextDrawer::FontColor::YellowGreen : ITextDrawer::FontColor::White,
+ ITextDrawer::Alignment::Right );
+ text_draw.Print(
+ param_x, y + Row::VerticalSync * y_step,
+ settings_.GetOrSetBool( SettingsKeys::vsync, false ) ? g_on : g_off,
+ scale,
+ current_row_ == Row::VerticalSync ? ITextDrawer::FontColor::Golden : ITextDrawer::FontColor::DarkYellow,
+ ITextDrawer::Alignment::Left );
+ text_draw.Print(
+ param_descr_x, y + Row::DrawFPS * y_step,
+ "Draw FPS", scale,
+ current_row_ == Row::DrawFPS ? ITextDrawer::FontColor::YellowGreen : ITextDrawer::FontColor::White,
+ ITextDrawer::Alignment::Right );
+ text_draw.Print(
+ param_x, y + Row::DrawFPS * y_step,
+ settings_.GetOrSetBool( SettingsKeys::draw_fps, false ) ? g_on : g_off,
+ scale,
+ current_row_ == Row::DrawFPS ? ITextDrawer::FontColor::Golden : ITextDrawer::FontColor::DarkYellow,
+ ITextDrawer::Alignment::Left );
+
if( !video_modes_.empty() && !video_modes_[display_].empty() && !video_modes_[display_][resolution_].supported_frequencies.empty() )
{
const SystemWindow::VideoMode& video_mode= video_modes_[display_][resolution_];
@@ -1763,13 +1831,13 @@ void VideoMenu::Draw( IMenuDrawer& menu_drawer, ITextDrawer& text_draw )
"Windowed height", scale,
current_row_ == Row::WindowHeight ? ITextDrawer::FontColor::YellowGreen : ITextDrawer::FontColor::White,
ITextDrawer::Alignment::Right );
+
std::snprintf( size_str, sizeof(size_str), current_row_ == Row::WindowHeight ? "%s_" : "%s", window_height_ );
text_draw.Print(
param_x, y + Row::WindowHeight * y_step,
size_str, scale,
current_row_ == Row::WindowHeight ? ITextDrawer::FontColor::Golden : ITextDrawer::FontColor::DarkYellow,
ITextDrawer::Alignment::Left );
-
text_draw.Print(
( param_descr_x + param_x ) / 2, y + Row::ApplyNow * y_step,
g_apply_now, scale,
@@ -1869,6 +1937,15 @@ void VideoMenu::Left()
}
break;
+ case Row::VerticalSync:
+ settings_.SetSetting( SettingsKeys::vsync, !settings_.GetBool( SettingsKeys::vsync, true ) );
+ PlayMenuSound( Sound::SoundId::MenuSelect );
+ break;
+ case Row::DrawFPS:
+ settings_.SetSetting( SettingsKeys::draw_fps, !settings_.GetBool( SettingsKeys::draw_fps, true ) );
+ PlayMenuSound( Sound::SoundId::MenuSelect );
+ break;
+
default:
break;
};
@@ -1955,6 +2032,15 @@ void VideoMenu::Right()
}
break;
+ case Row::VerticalSync:
+ settings_.SetSetting( SettingsKeys::vsync, !settings_.GetBool( SettingsKeys::vsync, true ) );
+ PlayMenuSound( Sound::SoundId::MenuSelect );
+ break;
+ case Row::DrawFPS:
+ settings_.SetSetting( SettingsKeys::draw_fps, !settings_.GetBool( SettingsKeys::draw_fps, true ) );
+ PlayMenuSound( Sound::SoundId::MenuSelect );
+ break;
+
default:
break;
};
@@ -1967,6 +2053,41 @@ MenuBase* VideoMenu::Select()
settings_.SetSetting( SettingsKeys::fullscreen, !settings_.GetBool( SettingsKeys::fullscreen, false ) );
PlayMenuSound( Sound::SoundId::MenuSelect );
break;
+ case Row::VerticalSync:
+ settings_.SetSetting( SettingsKeys::vsync, !settings_.GetBool( SettingsKeys::vsync, true ) );
+ PlayMenuSound( Sound::SoundId::MenuSelect );
+ break;
+ case Row::DrawFPS:
+ settings_.SetSetting( SettingsKeys::draw_fps, !settings_.GetBool( SettingsKeys::draw_fps, true ) );
+ PlayMenuSound( Sound::SoundId::MenuSelect );
+ break;
+ case Row::FullscreenResolution:
+ if( !video_modes_.empty() && video_modes_[display_].size() >= 2u )
+ {
+ const unsigned int prev_resolution= resolution_;
+ resolution_+= ( video_modes_[display_].size() - 1u );
+ resolution_%= video_modes_[display_].size();
+
+ // Change resolution - search same frequency for different resolution.
+ bool frequency_found= false;
+ for( unsigned int i= 0u; i < video_modes_[display_][resolution_].supported_frequencies.size(); i++ )
+ {
+ if( video_modes_[display_][prev_resolution].supported_frequencies[frequency_] ==
+ video_modes_[display_][resolution_].supported_frequencies[i] )
+ {
+ frequency_= i;
+ frequency_found= true;
+ break;
+ }
+ }
+ if( !frequency_found )
+ frequency_= 0u;
+
+ PlayMenuSound( Sound::SoundId::MenuSelect );
+ UpdateSettings();
+ }
+ break;
+
case Row::ApplyNow:
PlayMenuSound( Sound::SoundId::MenuSelect );
host_commands_.VidRestart();
@@ -2061,7 +2182,7 @@ class OptionsMenu final : public MenuBase
{
enum : int
{
- Controls, Video, Graphics, AlwaysRun, Crosshair, RevertMouse, WeaponReset, OldStylePerspective, Brightness, FXVolume, CDVolume, MouseSensitivity, FOV, NumRows
+ Controls, Video, Graphics, AlwaysRun, Crosshair, RevertMouse, WeaponReset, OldStylePerspective, SaveComment, Brightness, FXVolume, CDVolume, MouseSensitivity, FOV, NumRows
};
};
int current_row_= 0;
@@ -2071,6 +2192,7 @@ class OptionsMenu final : public MenuBase
bool reverse_mouse_;
bool weapon_reset_;
bool old_style_perspecive_;
+ std::string save_comment_= "time";
const int c_max_slider_value= 15;
int brightness_;
@@ -2202,6 +2324,16 @@ void OptionsMenu::Draw( IMenuDrawer& menu_drawer, ITextDrawer& text_draw )
current_row_ == Row::OldStylePerspective ? ITextDrawer::FontColor::Golden : ITextDrawer::FontColor::DarkYellow,
ITextDrawer::Alignment::Left );
+ text_draw.Print(
+ param_descr_x, y + Row::SaveComment * y_step,
+ "Save Comment", scale,
+ ITextDrawer::FontColor::White, ITextDrawer::Alignment::Right );
+ text_draw.Print(
+ param_x, y + Row::SaveComment * y_step,
+ save_comment_.c_str(), scale,
+ current_row_ == Row::SaveComment ? ITextDrawer::FontColor::Golden : ITextDrawer::FontColor::DarkYellow,
+ ITextDrawer::Alignment::Left );
+
char slider_back_text[ 1u + 7u + 1u + 1u ];
slider_back_text[0]= ITextDrawer::c_slider_left_letter_code;
std::memset( slider_back_text + 1u, ITextDrawer::c_slider_back_letter_code, 7u );
@@ -2371,6 +2503,11 @@ void OptionsMenu::Left()
PlayMenuSound( Sound::SoundId::MenuScroll );
}
break;
+ case Row::SaveComment:
+ save_comment_ = save_comment_ == "time" ? "hud" : "time";
+ settings_.SetSetting( SettingsKeys::save_comment, save_comment_.c_str() );
+ PlayMenuSound( Sound::SoundId::MenuSelect );
+ break;
default:
break;
}
@@ -2451,6 +2588,11 @@ void OptionsMenu::Right()
PlayMenuSound( Sound::SoundId::MenuScroll );
}
break;
+ case Row::SaveComment:
+ save_comment_ = save_comment_ == "time" ? "hud" : "time";
+ settings_.SetSetting( SettingsKeys::save_comment, save_comment_.c_str() );
+ PlayMenuSound( Sound::SoundId::MenuSelect );
+ break;
default:
break;
}
@@ -2494,6 +2636,11 @@ MenuBase* OptionsMenu::Select()
settings_.SetSetting( SettingsKeys::old_style_perspective, old_style_perspecive_ );
PlayMenuSound( Sound::SoundId::MenuSelect );
break;
+ case Row::SaveComment:
+ save_comment_ = save_comment_ == "time" ? "hud" : "time";
+ settings_.SetSetting( SettingsKeys::save_comment, save_comment_.c_str() );
+ PlayMenuSound( Sound::SoundId::MenuSelect );
+ break;
default:
break;
};
@@ -2732,6 +2879,7 @@ void Menu::ProcessEvents( const SystemEvents& events )
{
for( const SystemEvent& event : events )
{
+ const KeyChecker key_pressed( host_commands_.GetSettings(), event );
switch( event.type )
{
case SystemEvent::Type::MouseKey:
@@ -2767,36 +2915,35 @@ void Menu::ProcessEvents( const SystemEvents& events )
}
break;
case SystemEvent::Type::Key:
- if(event.event.key.pressed)
+ if( key_pressed( SettingsKeys::key_screenshot, KeyCode::F12 ) )
{
- switch( event.event.key.key_code )
+ if( current_menu_ != nullptr && !current_menu_->getInSetMode() )
+ host_commands_.GetSystemWindow()->CaptureScreen();
+ break;
+ }
+ if( key_pressed( SettingsKeys::key_menu_exit, KeyCode::Escape ) )
+ {
+ if( current_menu_ != nullptr )
{
- case KeyCode::Escape:
- if( current_menu_ != nullptr )
- {
- current_menu_->PlayMenuSound( Sound::SoundId::MenuOn );
- MenuBase* const new_menu= current_menu_->GetParent();
- if( new_menu != current_menu_ )
- {
- if( new_menu != nullptr )
- new_menu->OnActivated();
- current_menu_= new_menu;
- }
- }
- else
- {
- current_menu_= root_menu_.get();
- current_menu_->PlayMenuSound( Sound::SoundId::MenuOn );
- current_menu_->OnActivated();
- }
- break;
- default: break;
+ current_menu_->PlayMenuSound( Sound::SoundId::MenuOn );
+ MenuBase* const new_menu= current_menu_->GetParent();
+ if( new_menu != current_menu_ )
+ {
+ if( new_menu != nullptr )
+ new_menu->OnActivated();
+ current_menu_= new_menu;
+ }
+ }
+ else
+ {
+ current_menu_= root_menu_.get();
+ current_menu_->PlayMenuSound( Sound::SoundId::MenuOn );
+ current_menu_->OnActivated();
}
}
break;
default:
break;
-
};
if( current_menu_ != nullptr )
@@ -2818,66 +2965,47 @@ void Menu::ProcessEventsWhileNonactive( const SystemEvents& events )
return;
PC_ASSERT( current_menu_ == nullptr );
-
+ Settings& settings = host_commands_.GetSettings();
for( const SystemEvent& event : events )
{
- switch( event.type )
+ MenuBase* const previous_menu= current_menu_;
+ const KeyChecker key_pressed( settings, event );
+ if( key_pressed( SettingsKeys::key_save_menu, KeyCode::F2 ) )
{
- case SystemEvent::Type::Key:
- if( event.event.key.pressed )
- {
-
- MenuBase* const previous_menu= current_menu_;
- switch( event.event.key.key_code )
- {
- case KeyCode::F2:
- current_menu_= root_menu_->OpenSaveMenu();
- root_menu_->PlayMenuSound( Sound::SoundId::MenuSelect );
- break;
-
- case KeyCode::F3:
- current_menu_= root_menu_->OpenLoadMenu();
- root_menu_->PlayMenuSound( Sound::SoundId::MenuSelect );
- break;
-
- case KeyCode::F4:
- current_menu_= root_menu_->OpenOptionsMenu();
- root_menu_->PlayMenuSound( Sound::SoundId::MenuSelect );
- break;
-
- case KeyCode::F5:
- current_menu_= root_menu_->OpenNetworkMenu();
- root_menu_->PlayMenuSound( Sound::SoundId::MenuSelect );
- break;
-
- case KeyCode::F6:
- host_commands_.SaveGame(0u);
- break;
-
- case KeyCode::F9:
- host_commands_.LoadGame(0u);
- break;
-
- case KeyCode::F10:
- current_menu_= root_menu_->OpenQuitMenu();
- root_menu_->PlayMenuSound( Sound::SoundId::MenuSelect );
- break;
-
- case KeyCode::F12:
- // TODO - take screenshot
- break;
-
- default:
- break;
- };
+ current_menu_= root_menu_->OpenSaveMenu();
+ root_menu_->PlayMenuSound( Sound::SoundId::MenuSelect );
+ }
+ if( key_pressed( SettingsKeys::key_load_menu, KeyCode::F3 ) )
+ {
+ current_menu_= root_menu_->OpenLoadMenu();
+ root_menu_->PlayMenuSound( Sound::SoundId::MenuSelect );
+ }
+ if( key_pressed( SettingsKeys::key_options_menu, KeyCode::F4 ) )
+ {
+ current_menu_= root_menu_->OpenOptionsMenu();
+ root_menu_->PlayMenuSound( Sound::SoundId::MenuSelect );
+ }
+ if( key_pressed( SettingsKeys::key_network_menu, KeyCode::F5 ) )
+ {
+ current_menu_= root_menu_->OpenNetworkMenu();
+ root_menu_->PlayMenuSound( Sound::SoundId::MenuSelect );
+ }
- if( current_menu_ != nullptr && current_menu_ != previous_menu )
- current_menu_->OnActivated();
- }
- break;
- default:
- break;
+ if( key_pressed( SettingsKeys::key_save_game, KeyCode::F6 ) )
+ {
+ host_commands_.SaveGame(0u);
+ }
+ if( key_pressed( SettingsKeys::key_load_game, KeyCode::F9 ) )
+ {
+ host_commands_.LoadGame(0u);
+ }
+ if( key_pressed( SettingsKeys::key_quit_menu, KeyCode::F10 ) )
+ {
+ current_menu_= root_menu_->OpenQuitMenu();
+ root_menu_->PlayMenuSound( Sound::SoundId::MenuSelect );
}
+ if( current_menu_ != nullptr && current_menu_ != previous_menu )
+ current_menu_->OnActivated();
}
}
diff --git a/src/model.cpp b/src/model.cpp
index 30cb4444..5e34c28b 100644
--- a/src/model.cpp
+++ b/src/model.cpp
@@ -39,20 +39,15 @@ SIZE_ASSERT( Vertex_o3, 6 );
struct CARHeader
{
- static constexpr unsigned int c_sound_count= 7u;
-
- unsigned short animations[20u];
- unsigned short submodels_animations[3u][2u];
-
- // 6, 7, 8 - gibs type
- unsigned short unknown0[9];
-
- // Values - sound data size, in bytes.
- unsigned short sounds[c_sound_count];
-
- // 0 - always zero, maybe
- // 1 - 8 values, like 64, 96, 128 etc.
- unsigned short unknown1[9];
+ // AniMap
+ std::array< unsigned short, 20 > animations;
+ std::array< std::array, 6 > submodels_animations;
+ // GSND sound id
+ std::array< unsigned short, 3 > sounds;
+ // SFXSize sound length in bytes
+ std::array< unsigned short, 8 > sfx_len;
+ // SFXVol sound volume
+ std::array< unsigned short, 8 > sfx_vol;
};
SIZE_ASSERT( CARHeader, 0x66 );
@@ -431,14 +426,14 @@ void LoadModel_car( const Vfs::FileContent& model_file, Model& out_model )
out_model.texture_data.size() +
out_model.frame_count * sizeof(Vertex_o3) * vertex_count;
- out_model.submodels.resize(3u);
+ out_model.submodels.resize(header->submodels_animations.size());
for( Submodel& submodel : out_model.submodels )
{
submodel.frame_count= 0u;
submodel.z_min= submodel.z_max= 0.0f;
}
- for( unsigned int i= 0u; i < 3u; i++ )
+ for( unsigned int i= 0u; i < header->submodels_animations.size(); i++ )
{
const unsigned int c_animation_data_offset= 0x4806u;
@@ -465,7 +460,7 @@ void LoadModel_car( const Vfs::FileContent& model_file, Model& out_model )
// Setup animations.
// Each submodel have up to 2 animations.
unsigned int first_submodel_animation_frame= 0u;
- for( unsigned int a= 0u; a < 2u; a++ )
+ for( unsigned int a= 0u; a < header->submodels_animations[i].size(); a++ )
{
const unsigned int animation_frame_count=
header->submodels_animations[i][a] / ( sizeof(Vertex_o3) * vertex_count );
@@ -484,19 +479,21 @@ void LoadModel_car( const Vfs::FileContent& model_file, Model& out_model )
submodels_offset+= c_animation_data_offset + submodel_animation_data_size;
} // for submodels
- unsigned int sounds_offset= submodels_offset;
-
- out_model.sounds.resize( CARHeader::c_sound_count );
- for( unsigned int i= 0u; i < CARHeader::c_sound_count; i++ )
+ for( unsigned int i= 0u; i < header->sfx_len.size(); i++ )
{
std::vector& sound= out_model.sounds[i];
- sound.resize( header->sounds[i] );
- std::memcpy( sound.data(), model_file.data() + sounds_offset, header->sounds[i] );
+ sound.resize( header->sfx_len[i] );
+ std::memcpy( sound.data(), model_file.data() + submodels_offset, header->sfx_len[i] );
- sounds_offset+= header->sounds[i];
+ submodels_offset+= header->sfx_len[i];
}
+ PC_ASSERT( submodels_offset == model_file.size() );
+}
+
+void LoadModel_gltf( const Vfs::FileContent& model_file, Model& out_model )
+{
+ ClearModel( out_model );
- PC_ASSERT( sounds_offset == model_file.size() );
}
} // namespace ChasmReverse
diff --git a/src/model.hpp b/src/model.hpp
index a16e3ece..965b2e8e 100644
--- a/src/model.hpp
+++ b/src/model.hpp
@@ -1,8 +1,6 @@
#pragma once
#include
-
#include
-
#include "assert.hpp"
#include "vfs.hpp"
@@ -55,7 +53,8 @@ struct Submodel
std::vector animations_bboxes;
// Associated with models sounds (raw PCM)
- std::vector< std::vector > sounds;
+ std::array< std::vector, 8 > sounds;
+ std::array< unsigned char, 3 > sound_monster_id;
float z_min, z_max;
};
@@ -75,5 +74,6 @@ void LoadModel_o3(
Model& out_model );
void LoadModel_car( const Vfs::FileContent& model_file, Model& out_model );
+void LoadModel_gltf( const Vfs::FileContent& model_file, Model& out_model );
} // namespace ChasmReverse
diff --git a/src/save_load.cpp b/src/save_load.cpp
index 40bb241b..ce868353 100644
--- a/src/save_load.cpp
+++ b/src/save_load.cpp
@@ -1,14 +1,6 @@
#include
#include
-// Include OS-dependend stuff for "mkdir".
-#ifdef _WIN32
-#include
-#else
-#include
-#include
-#endif
-
#include "common/files.hpp"
using namespace ChasmReverse;
@@ -16,7 +8,6 @@ using namespace ChasmReverse;
#include "save_load.hpp"
-#define SAVES_DIR "saves"
namespace PanzerChasm
{
@@ -61,14 +52,14 @@ SaveHeader::HashType SaveHeader::CalculateHash( const unsigned char* data, unsig
}
bool SaveData(
- const char* file_name,
+ const std::filesystem::path& file_name,
const SaveComment& save_comment,
const SaveLoadBuffer& data )
{
- FILE* f= std::fopen( file_name, "wb" );
+ FILE* f= std::fopen( file_name.native().c_str(), "wb" );
if( f == nullptr )
{
- Log::Warning( "Can not write save \"", file_name, "\"" );
+ Log::Warning( "Can not write save \"", file_name.native(), "\"" );
return false;
}
@@ -88,13 +79,13 @@ bool SaveData(
// Returns true, if all ok
bool LoadData(
- const char* file_name,
+ const std::filesystem::path& file_name,
SaveLoadBuffer& out_data )
{
- FILE* const f= std::fopen( file_name, "rb" );
+ FILE* const f= std::fopen( file_name.native().c_str(), "rb" );
if( f == nullptr )
{
- Log::Warning( "Can not read save \"", file_name, "\"." );
+ Log::Warning( "Can not read save \"", file_name.native(), "\"." );
return false;
}
@@ -153,10 +144,10 @@ bool LoadData(
}
bool LoadSaveComment(
- const char* file_name,
+ const std::filesystem::path& file_name,
SaveComment& out_save_comment )
{
- FILE* const f= std::fopen( file_name, "rb" );
+ FILE* const f= std::fopen( file_name.native().c_str(), "rb" );
if( f == nullptr )
{
return false;
@@ -179,21 +170,58 @@ bool LoadSaveComment(
return true;
}
-void GetSaveFileNameForSlot(
- const unsigned int slot_number,
- char* const out_file_name,
- const unsigned int out_file_name_max_length )
+std::filesystem::path GetSaveFileNameForSlot(const uint8_t slot_number)
+{
+ static char tmp[12] = "save_";
+ std::snprintf( tmp, sizeof(tmp), "save_%02hhu.pcs", slot_number );
+ return std::filesystem::absolute(SAVES_DIR / tmp);
+}
+
+std::filesystem::path GetScreenShotFileNameForSlot( const uint8_t slot_number, const std::filesystem::path& shot_name )
+{
+ static char tmp[12] = "shot_";
+ std::snprintf( tmp, sizeof(tmp), "%4s_%02hhu.tga", shot_name.native().c_str(), slot_number );
+ return std::filesystem::absolute(SAVES_DIR / tmp);
+}
+
+uint8_t GetScreenShotSlotNumber( const std::filesystem::path& saves_dir )
{
- std::snprintf( out_file_name, out_file_name_max_length, SAVES_DIR"/save_%02d.pcs", slot_number );
+ static uint8_t slot_number = 0;
+ int8_t slot_disk = -1;
+ for( const auto & entry : std::filesystem::directory_iterator( std::filesystem::absolute( SAVES_DIR ) ) )
+ {
+ if( entry.path().extension() == ".tga" )
+ {
+ const std::filesystem::path& shot_name = remove_extension(entry.path().filename());
+ if( shot_name.native().size() > 6 )
+ {
+ const char digits[3] = { shot_name.native().c_str()[5], shot_name.native().c_str()[6], '\0' };
+ if( isdigit( digits[0] ) && isdigit( digits[1] ) )
+ {
+ const uint8_t num = atoi( digits );
+ if( num > slot_disk ) slot_disk = num;
+
+ }
+ }
+ }
+
+ }
+
+ slot_disk++;
+ if(slot_disk == 0) return 0;
+
+ if(slot_disk > 99)
+ slot_number = slot_number > 99 ? 0 : slot_number;
+ else
+ slot_number = slot_disk;
+
+ return slot_number++;
}
void CreateSlotSavesDir()
{
-#ifdef _WIN32
- _mkdir( SAVES_DIR );
-#else
- mkdir( SAVES_DIR, 0777 );
-#endif
+ if(!std::filesystem::exists(SAVES_DIR) && !std::filesystem::create_directories( SAVES_DIR ))
+ Log::Warning("Couldn't create saves directory: ", strerror(errno));
}
} // namespace PanzerChasm
diff --git a/src/save_load.hpp b/src/save_load.hpp
index c9640000..181fb57a 100644
--- a/src/save_load.hpp
+++ b/src/save_load.hpp
@@ -1,7 +1,10 @@
#pragma once
+#include
#include "assert.hpp"
#include "fwd.hpp"
+const std::filesystem::path SAVES_DIR = "saves";
+
namespace PanzerChasm
{
@@ -27,23 +30,22 @@ SIZE_ASSERT( SaveHeader, 20u );
// Returns true, if all ok
bool SaveData(
- const char* file_name,
+ const std::filesystem::path& file_name,
const SaveComment& save_comment,
const SaveLoadBuffer& data );
// Returns true, if all ok
bool LoadData(
- const char* file_name,
+ const std::filesystem::path& file_name,
SaveLoadBuffer& out_data );
bool LoadSaveComment(
- const char* file_name,
+ const std::filesystem::path& file_name,
SaveComment& out_save_comment );
-void GetSaveFileNameForSlot(
- unsigned int slot_number,
- char* out_file_name,
- unsigned int out_file_name_max_length );
+std::filesystem::path GetSaveFileNameForSlot( const uint8_t slot_number );
+std::filesystem::path GetScreenShotFileNameForSlot( const uint8_t slot_number, const std::filesystem::path& shot_name = "shot" );
+uint8_t GetScreenShotSlotNumber( const std::filesystem::path& saves_dir = SAVES_DIR );
void CreateSlotSavesDir();
diff --git a/src/shared_settings_keys.hpp b/src/shared_settings_keys.hpp
index 91c5c7de..ee91ac64 100644
--- a/src/shared_settings_keys.hpp
+++ b/src/shared_settings_keys.hpp
@@ -9,26 +9,60 @@ namespace SettingsKeys
const char always_run[]= "sv_always_run";
const char crosshair[]= "cl_crosshair";
const char reverse_mouse[]= "in_reverse_mouse";
+const char perm_mlook[]= "in_perm_mlook";
const char weapon_reset[]= "cl_weapon_reset";
const char old_style_perspective[]= "cl_old_style_perspective";
const char player_color[]= "cl_color";
const char player_name[]= "cl_name";
-
+const char save_comment[]= "save_comment";
const char fx_volume[]= "s_volume";
const char cd_volume[]= "cd_volume";
const char mouse_sensetivity[]= "cl_mouse_speed";
const char fov[]= "cl_fov";
+/* essential keys */
const char key_forward[]= "in_key_forward";
const char key_backward[]= "in_key_backward";
const char key_step_left[]= "in_key_left";
const char key_step_right[]= "in_key_right";
+const char key_speed_up[]= "in_key_speed_up";
+const char key_always_run[]= "in_key_always_run";
+const char key_jump[]= "in_key_jump";
+const char key_fire[]= "in_key_fire";
+const char key_weapon_1[]= "in_key_weapon_1";
+const char key_weapon_2[]= "in_key_weapon_2";
+const char key_weapon_3[]= "in_key_weapon_3";
+const char key_weapon_4[]= "in_key_weapon_4";
+const char key_weapon_5[]= "in_key_weapon_5";
+const char key_weapon_6[]= "in_key_weapon_6";
+const char key_weapon_7[]= "in_key_weapon_7";
+const char key_weapon_8[]= "in_key_weapon_8";
+const char key_weapon_9[]= "in_key_weapon_9";
+const char key_minimap[]= "in_key_minimap";
+const char key_minimap_scale_dec[]= "in_key_minimap_scale_dec";
+const char key_minimap_scale_inc[]= "in_key_minimap_scale_inc";
+const char key_small_hud_off[]= "in_key_small_hud_off";
+const char key_small_hud_on[]= "in_key_small_hud_on";
+const char key_weapon_change[]= "in_key_weapon_change";
+const char key_weapon_next[]= "in_key_weapon_next";
+const char key_weapon_prev[]= "in_key_weapon_prev";
+const char key_perm_mlook[]= "in_key_perm_mlook";
+const char key_temp_mlook[]= "in_key_temp_mlook";
+const char key_strafe_on[]= "in_key_strafe_on";
const char key_turn_left[]= "in_key_turn_left";
const char key_turn_right[]= "in_key_turn_right";
const char key_look_up[]= "in_key_look_up";
+const char key_center_view[]= "in_key_center_view";
const char key_look_down[]= "in_key_look_down";
-const char key_jump[]= "in_key_jump";
-
+const char key_load_game[]= "in_key_load_game";
+const char key_save_game[]= "in_key_save_game";
+const char key_load_menu[]= "in_key_load_menu";
+const char key_save_menu[]= "in_key_save_menu";
+const char key_options_menu[]= "in_key_options_menu";
+const char key_network_menu[]= "in_key_network_menu";
+const char key_quit_menu[]= "in_key_quit_menu";
+const char key_screenshot[]= "in_key_screenshot";
+const char key_menu_exit[]= "in_key_menu_exit";
const char window_width []= "r_window_width" ;
const char window_height[]= "r_window_height";
const char fullscreen[]= "r_fullscreen";
@@ -36,7 +70,8 @@ const char fullscreen_display[]= "r_fullscreen_display";
const char fullscreen_width []= "r_fullscreen_width" ;
const char fullscreen_height[]= "r_fullscreen_height";
const char fullscreen_frequency[]= "r_fullscreen_frequency";
-
+const char vsync[]= "r_gl_vsync";
+const char draw_fps[]= "cl_draw_fps";
const char software_rendering[]= "r_software_rendering";
const char software_scale[]= "r_software_scale";
@@ -48,7 +83,6 @@ const char opengl_msaa_level[]= "r_msaa_level";
const char shadows[]= "r_shadows";
const char brightness[]= "r_brightness";
-
} // namespace SettingsKeys
} // PanzerChasm
diff --git a/src/size.hpp b/src/size.hpp
index 53c32ae5..2e693fdc 100644
--- a/src/size.hpp
+++ b/src/size.hpp
@@ -1,5 +1,8 @@
#pragma once
+#include
+#include
+
namespace PanzerChasm
{
diff --git a/src/sound/sounds_loader.cpp b/src/sound/sounds_loader.cpp
index 1c062c67..64780faf 100644
--- a/src/sound/sounds_loader.cpp
+++ b/src/sound/sounds_loader.cpp
@@ -7,6 +7,7 @@
#include "../vfs.hpp"
#include "sounds_loader.hpp"
+#include "common/files.hpp"
namespace PanzerChasm
{
@@ -17,16 +18,6 @@ namespace Sound
namespace
{
-const char* ExtractExtension( const char* const file_path )
-{
- unsigned int pos= std::strlen( file_path );
-
- while( pos > 0u && file_path[ pos ] != '.' )
- pos--;
-
- return file_path + pos + 1u;
-}
-
class RawPCMSoundData final : public ISoundData
{
public:
@@ -143,16 +134,15 @@ ISoundDataConstPtr LoadSound( const char* file_path, Vfs& vfs )
Log::Warning( "Can not load \"", file_path, "\"" );
return nullptr;
}
+ const char* const extension = ExtractExtension( file_path );
- const char* const extension= ExtractExtension( file_path );
-
- if( std::strcmp( extension, "WAV" ) == 0 ||
- std::strcmp( extension, "wav" ) == 0 )
- {
- return ISoundDataConstPtr( new WavSoundData( file_content ) );
- }
+ if( strncmp( extension, "WAV", 3 ) == 0 || strncmp( extension, "wav", 3 ) == 0 )
+ return ISoundDataConstPtr( new WavSoundData( std::move( file_content ) ) );
else // *.SFX, *.PCM, *.RAW files
- return ISoundDataConstPtr( new RawPCMSoundData( std::move( file_content ) ) );
+ if( vfs.archive_.type == Vfs::TAR )
+ return ISoundDataConstPtr( new WavSoundData( std::move( file_content ) ) );
+ else
+ return ISoundDataConstPtr( new RawPCMSoundData( std::move( file_content ) ) );
return nullptr;
}
diff --git a/src/system_event.cpp b/src/system_event.cpp
index c4256f39..c289633b 100644
--- a/src/system_event.cpp
+++ b/src/system_event.cpp
@@ -18,6 +18,16 @@ const char* GetKeyName( const SystemEvent::KeyEvent::KeyCode key_code )
case KeyCode::Space: return "Space";
case KeyCode::Backspace: return "Backspace";
case KeyCode::Tab: return "Tab";
+ case KeyCode::CapsLock: return "CapsLock";
+ case KeyCode::LeftControl: return "LControl";
+ case KeyCode::RightControl: return "RControl";
+ case KeyCode::LeftAlt: return "LAlt";
+ case KeyCode::RightAlt: return "RAlt";
+ case KeyCode::LeftShift: return "LShift";
+ case KeyCode::RightShift: return "RShift";
+ case KeyCode::LeftMetaGUI: return "LMetaGUI";
+ case KeyCode::RightMetaGUI: return "RMetaGUI";
+ case KeyCode::Application: return "App";
case KeyCode::PageUp: return "PageUp";
case KeyCode::PageDown: return "PageDown";
@@ -55,7 +65,6 @@ const char* GetKeyName( const SystemEvent::KeyEvent::KeyCode key_code )
case KeyCode::Y: return "X";
case KeyCode::Z: return "Z";
- case KeyCode::K0: return "0";
case KeyCode::K1: return "1";
case KeyCode::K2: return "2";
case KeyCode::K3: return "3";
@@ -65,12 +74,13 @@ const char* GetKeyName( const SystemEvent::KeyEvent::KeyCode key_code )
case KeyCode::K7: return "7";
case KeyCode::K8: return "8";
case KeyCode::K9: return "9";
+ case KeyCode::K0: return "0";
case KeyCode::Minus: return "-";
case KeyCode::Equals: return "=";
- case KeyCode::SquareBrackretLeft: return "[";
- case KeyCode::SquareBrackretRight: return "]";
+ case KeyCode::SquareBracketLeft: return "[";
+ case KeyCode::SquareBracketRight: return "]";
case KeyCode::Semicolon: return ";";
case KeyCode::Apostrophe: return "'";
@@ -93,7 +103,35 @@ const char* GetKeyName( const SystemEvent::KeyEvent::KeyCode key_code )
case KeyCode::F11: return "F11";
case KeyCode::F12: return "F12";
+ case KeyCode::KPDivide: return "KPDivide";
+ case KeyCode::KPMultiply: return "KPMultiply";
+ case KeyCode::KPMinus: return "KPMinus";
+ case KeyCode::KPPlus: return "KPPlus";
+ case KeyCode::KPEnter: return "KPEnter";
+ case KeyCode::KP1: return "KP1";
+ case KeyCode::KP2: return "KP2";
+ case KeyCode::KP3: return "KP3";
+ case KeyCode::KP4: return "KP4";
+ case KeyCode::KP5: return "KP5";
+ case KeyCode::KP6: return "KP6";
+ case KeyCode::KP7: return "KP7";
+ case KeyCode::KP8: return "KP8";
+ case KeyCode::KP9: return "KP9";
+ case KeyCode::KP0: return "KP0";
+ case KeyCode::KPPeriod: return "KPPeriod";
+
case KeyCode::Pause: return "Pause";
+
+ case KeyCode::MouseUnknown: return "MUnknown";
+ case KeyCode::Mouse1: return "Mouse1";
+ case KeyCode::Mouse2: return "Mouse2";
+ case KeyCode::Mouse3: return "Mouse3";
+ case KeyCode::Mouse4: return "Mouse4";
+ case KeyCode::Mouse5: return "Mouse5";
+ case KeyCode::Mouse6: return "Mouse6";
+ case KeyCode::MouseWheelUp: return "MWheelUp";
+ case KeyCode::MouseWheelDown: return "MWheelDn";
+
};
PC_ASSERT(false);
@@ -105,8 +143,7 @@ bool KeyCanBeUsedForControl( const SystemEvent::KeyEvent::KeyCode key_code )
using KeyCode= SystemEvent::KeyEvent::KeyCode;
switch( key_code )
{
- case KeyCode::Enter:
- case KeyCode::Space:
+ case KeyCode::Unknown:
case KeyCode::PageUp:
case KeyCode::PageDown:
@@ -144,20 +181,6 @@ bool KeyCanBeUsedForControl( const SystemEvent::KeyEvent::KeyCode key_code )
case KeyCode::Y:
case KeyCode::Z:
- case KeyCode::Semicolon:
- case KeyCode::Apostrophe:
- case KeyCode::BackSlash:
- case KeyCode::Comma:
- case KeyCode::Period:
- case KeyCode::Slash:
-
- return true;
-
- case KeyCode::Escape: // menu navigation key
- case KeyCode::Backspace:
- case KeyCode::Tab: // minimap
-
- case KeyCode::K0: // Numbers keys used for weapon select
case KeyCode::K1:
case KeyCode::K2:
case KeyCode::K3:
@@ -167,11 +190,65 @@ bool KeyCanBeUsedForControl( const SystemEvent::KeyEvent::KeyCode key_code )
case KeyCode::K7:
case KeyCode::K8:
case KeyCode::K9:
+ case KeyCode::K0: // Numbers keys used for weapon select
+ case KeyCode::Semicolon:
+ case KeyCode::Apostrophe:
+ case KeyCode::BackSlash:
+ case KeyCode::Comma:
+ case KeyCode::Period:
+ case KeyCode::Slash:
+ case KeyCode::SquareBracketLeft: // [ and ] used for map scaling
+ case KeyCode::SquareBracketRight:
+ case KeyCode::Enter:
+ case KeyCode::Space:
+ case KeyCode::Tab: // minimap
+ case KeyCode::CapsLock:
+ case KeyCode::LeftControl:
+ case KeyCode::RightControl:
+ case KeyCode::LeftAlt:
+ case KeyCode::RightAlt:
+ case KeyCode::LeftShift:
+ case KeyCode::RightShift:
+ case KeyCode::LeftMetaGUI: // menu back
+ case KeyCode::RightMetaGUI: // menu select
+ case KeyCode::Application:
+
+ case KeyCode::MouseUnknown:
+ case KeyCode::Mouse1:
+ case KeyCode::Mouse2:
+ case KeyCode::Mouse3:
+ case KeyCode::Mouse4:
+ case KeyCode::Mouse5:
+ case KeyCode::Mouse6:
+ case KeyCode::MouseWheelUp:
+ case KeyCode::MouseWheelDown:
+ case KeyCode::KeyCount:
+ case KeyCode::Escape: // menu navigation key
+ case KeyCode::Backspace: // clear entrance
+ case KeyCode::KPDivide:
+ case KeyCode::KPMultiply:
+ case KeyCode::KPMinus:
+ case KeyCode::KPPlus:
+ case KeyCode::KPEnter:
+ case KeyCode::KP1:
+ case KeyCode::KP2:
+ case KeyCode::KP3:
+ case KeyCode::KP4:
+ case KeyCode::KP5:
+ case KeyCode::KP6:
+ case KeyCode::KP7:
+ case KeyCode::KP8:
+ case KeyCode::KP9:
+ case KeyCode::KP0:
+ case KeyCode::KPPeriod:
+ case KeyCode::F7:
+ case KeyCode::F8:
+ case KeyCode::F11:
+ case KeyCode::F12:
+ return true;
case KeyCode::Minus: // +- used for hud scaling
case KeyCode::Equals:
- case KeyCode::SquareBrackretLeft: // [ and ] used for map scaling
- case KeyCode::SquareBrackretRight:
case KeyCode::F1: // Functional keys used for quick commands
case KeyCode::F2:
@@ -179,17 +256,11 @@ bool KeyCanBeUsedForControl( const SystemEvent::KeyEvent::KeyCode key_code )
case KeyCode::F4:
case KeyCode::F5:
case KeyCode::F6:
- case KeyCode::F7:
- case KeyCode::F8:
case KeyCode::F9:
case KeyCode::F10:
- case KeyCode::F11:
- case KeyCode::F12:
case KeyCode::Pause: // Pause key used for pause (C.O.)
- case KeyCode::Unknown:
- case KeyCode::KeyCount:
return false;
};
diff --git a/src/system_event.hpp b/src/system_event.hpp
index 696f2858..83842829 100644
--- a/src/system_event.hpp
+++ b/src/system_event.hpp
@@ -19,7 +19,7 @@ struct SystemEvent
struct KeyEvent
{
- enum class KeyCode
+ enum KeyCode : char32_t
{
// Do not set here key names manually.
Unknown= 0,
@@ -40,29 +40,45 @@ struct SystemEvent
BackQuote,
A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z,
- K0, K1, K2, K3, K4, K5, K6, K7, K8, K9,
+ K1, K2, K3, K4, K5, K6, K7, K8, K9, K0,
// Put new keys at back.
Minus, Equals,
- SquareBrackretLeft, SquareBrackretRight,
+ SquareBracketLeft, SquareBracketRight,
Semicolon, Apostrophe, BackSlash,
Comma, Period, Slash,
F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12,
+ KPDivide, KPMultiply, KPMinus, KPPlus, KPEnter,
+ KP1, KP2, KP3, KP4, KP5, KP6, KP7, KP8, KP9, KP0, KPPeriod,
+
Pause,
+ CapsLock,
+ LeftControl,
+ RightControl,
+ LeftAlt,
+ RightAlt,
+ LeftShift,
+ RightShift,
+ LeftMetaGUI,
+ RightMetaGUI,
+ Application,
+
+ MouseUnknown, Mouse1, Mouse2, Mouse3, Mouse4, Mouse5, Mouse6,
+ MouseWheelUp, MouseWheelDown,
// Put it last here.
KeyCount
};
- enum Modifiers : unsigned int
+ enum Modifiers : char32_t
{
Shift= 0x01u,
Control= 0x02u,
Alt= 0x04u,
Caps= 0x08u,
};
- typedef unsigned int ModifiersMask;
+ typedef char32_t ModifiersMask;
KeyCode key_code;
ModifiersMask modifiers;
@@ -76,10 +92,17 @@ struct SystemEvent
struct MouseKeyEvent
{
- enum class Button
+ enum Button : char32_t
{
Unknown= 0,
- Left, Right, Middle,
+ Left=1, Mouse1=1,
+ Right=2, Mouse2=2,
+ Middle=3, Mouse3=3,
+ Mouse4=4,
+ Mouse5=5,
+ Mouse6=6,
+ MouseWheelUp=7,
+ MouseWheelDown=8,
ButtonCount
};
diff --git a/src/system_window.cpp b/src/system_window.cpp
index f53c6886..ce3a14b2 100644
--- a/src/system_window.cpp
+++ b/src/system_window.cpp
@@ -1,6 +1,7 @@
#include
#include
-
+#include
+#include
#include
#include "assert.hpp"
@@ -10,6 +11,9 @@
#include "shared_settings_keys.hpp"
#include "system_window.hpp"
+#include "common/tga.hpp"
+#include "common/files.hpp"
+#include "save_load.hpp"
namespace PanzerChasm
{
@@ -38,28 +42,50 @@ static SystemEvent::KeyEvent::KeyCode TranslateKey( const SDL_Scancode scan_code
case SDL_SCANCODE_MINUS: return KeyCode::Minus;
case SDL_SCANCODE_EQUALS: return KeyCode::Equals;
- case SDL_SCANCODE_LEFTBRACKET: return KeyCode::SquareBrackretLeft;
- case SDL_SCANCODE_RIGHTBRACKET: return KeyCode::SquareBrackretRight;
+ case SDL_SCANCODE_LEFTBRACKET: return KeyCode::SquareBracketLeft;
+ case SDL_SCANCODE_RIGHTBRACKET: return KeyCode::SquareBracketRight;
case SDL_SCANCODE_SEMICOLON: return KeyCode::Semicolon;
case SDL_SCANCODE_APOSTROPHE: return KeyCode::Apostrophe;
case SDL_SCANCODE_BACKSLASH: return KeyCode::BackSlash;
-
+ case SDL_SCANCODE_CAPSLOCK: return KeyCode::CapsLock;
+ case SDL_SCANCODE_LCTRL: return KeyCode::LeftControl;
+ case SDL_SCANCODE_RCTRL: return KeyCode::RightControl;
+ case SDL_SCANCODE_LALT: return KeyCode::LeftAlt;
+ case SDL_SCANCODE_RALT: return KeyCode::RightAlt;
+ case SDL_SCANCODE_LSHIFT: return KeyCode::LeftShift;
+ case SDL_SCANCODE_RSHIFT: return KeyCode::RightShift;
case SDL_SCANCODE_COMMA: return KeyCode::Comma;
case SDL_SCANCODE_PERIOD: return KeyCode::Period;
case SDL_SCANCODE_SLASH: return KeyCode::Slash;
-
+ case SDL_SCANCODE_LGUI: return KeyCode::LeftMetaGUI;
+ case SDL_SCANCODE_RGUI: return KeyCode::RightMetaGUI;
+ case SDL_SCANCODE_APPLICATION: return KeyCode::Application;
case SDL_SCANCODE_PAUSE: return KeyCode::Pause;
-
+ case SDL_SCANCODE_KP_DIVIDE: return KeyCode::KPDivide;
+ case SDL_SCANCODE_KP_MULTIPLY: return KeyCode::KPMultiply;
+ case SDL_SCANCODE_KP_MINUS: return KeyCode::KPMinus;
+ case SDL_SCANCODE_KP_PLUS: return KeyCode::KPPlus;
+ case SDL_SCANCODE_KP_ENTER: return KeyCode::KPEnter;
+ case SDL_SCANCODE_KP_1: return KeyCode::KP1;
+ case SDL_SCANCODE_KP_2: return KeyCode::KP2;
+ case SDL_SCANCODE_KP_3: return KeyCode::KP3;
+ case SDL_SCANCODE_KP_4: return KeyCode::KP4;
+ case SDL_SCANCODE_KP_5: return KeyCode::KP5;
+ case SDL_SCANCODE_KP_6: return KeyCode::KP6;
+ case SDL_SCANCODE_KP_7: return KeyCode::KP7;
+ case SDL_SCANCODE_KP_8: return KeyCode::KP8;
+ case SDL_SCANCODE_KP_9: return KeyCode::KP9;
+ case SDL_SCANCODE_KP_0: return KeyCode::KP0;
+ case SDL_SCANCODE_KP_PERIOD: return KeyCode::KPPeriod;
default:
if( scan_code >= SDL_SCANCODE_A && scan_code <= SDL_SCANCODE_Z )
- return KeyCode( int(KeyCode::A) + (scan_code - SDL_SCANCODE_A) );
- if( scan_code >= SDL_SCANCODE_1 && scan_code <= SDL_SCANCODE_9 )
- return KeyCode( int(KeyCode::K1) + (scan_code - SDL_SCANCODE_1) );
- if( scan_code == SDL_SCANCODE_0 )
- return KeyCode::K0;
- if( scan_code >= SDL_SCANCODE_F1 && scan_code <= SDL_SCANCODE_F12 )
+ return KeyCode( KeyCode::A + (scan_code - SDL_SCANCODE_A) );
+ else if( scan_code >= SDL_SCANCODE_1 && scan_code <= SDL_SCANCODE_0 )
+ return KeyCode( KeyCode::K1 + (scan_code - SDL_SCANCODE_1) );
+ else if( scan_code >= SDL_SCANCODE_F1 && scan_code <= SDL_SCANCODE_F12 )
return KeyCode( int(KeyCode::F1) + (scan_code - SDL_SCANCODE_F1) );
+ break;
};
return KeyCode::Unknown;
@@ -286,16 +312,16 @@ SystemWindow::SystemWindow( Settings& settings )
{
// In this mode we required most simple opengl - 1.1 version. It does`nt requires any flags.
}
+ SDL_ClearError();
+ if(SDL_CreateWindowAndRenderer(width, height,( (is_opengl || use_gl_context_for_software_renderer_) ? SDL_WINDOW_OPENGL : 0 ) | ( fullscreen ? SDL_WINDOW_FULLSCREEN : 0 ) | SDL_WINDOW_SHOWN, &window_, &renderer_) != 0)
+ {
+ SDL_Log("Can not create window: %s\n", SDL_GetError());
+ }
- window_=
- SDL_CreateWindow(
- "PanzerChasm",
- SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
- width, height,
- ( (is_opengl || use_gl_context_for_software_renderer_) ? SDL_WINDOW_OPENGL : 0 ) | ( fullscreen ? SDL_WINDOW_FULLSCREEN : 0 ) | SDL_WINDOW_SHOWN );
-
- if( window_ == nullptr )
+ if( window_ == nullptr || renderer_ == nullptr )
+ {
Log::FatalError( "Can not create window" );
+ }
if( is_opengl || use_gl_context_for_software_renderer_ )
{
@@ -358,7 +384,7 @@ SystemWindow::SystemWindow( Settings& settings )
// Do reinterpret_cast, because on different platforms arguments of GLDEBUGPROC have
// different const qualifier. Just ignore this cualifiers - we always have 'const'.
if( glDebugMessageCallback != nullptr )
- glDebugMessageCallback( reinterpret_cast(&GLDebugMessageCallback), NULL );
+ glDebugMessageCallback( reinterpret_cast(&GLDebugMessageCallback), nullptr );
#endif
}
else if( use_gl_context_for_software_renderer_ )
@@ -794,4 +820,76 @@ void SystemWindow::UpdateBrightness()
}
}
-} // namespace PanzerChasm
+bool SystemWindow::CaptureScreen( const std::filesystem::path& file ) const
+{
+ CreateSlotSavesDir();
+ uint8_t slot_number = GetScreenShotSlotNumber();
+ std::filesystem::path dst = GetScreenShotFileNameForSlot( slot_number, file );
+ int result = -1;
+#if SDL_BYTEORDER == SDL_BIG_ENDIAN
+ std::array mask = { 0xff000000, 0x00ff0000, 0x0000ff00, 0x000000ff };
+#else
+ std::array mask = { 0x000000ff, 0x0000ff00, 0x00ff0000, 0xff000000 };
+#endif
+ /* get screen surface from window */
+ SDL_Surface* screen = SDL_GetWindowSurface(window_);
+ int size = screen->w * screen->h;
+ std::vector> pixels(size);
+
+ /* get opengl settings */
+ const bool is_opengl= !settings_.GetOrSetBool( SettingsKeys::software_rendering, true );
+
+ if (!pixels.empty())
+ {
+ if( is_opengl || use_gl_context_for_software_renderer_ )
+ {
+ /* set pack alignment */
+ GLint pack_aligment;
+ glGetIntegerv(GL_PACK_ALIGNMENT, &pack_aligment);
+ glPixelStorei(GL_PACK_ALIGNMENT, 4);
+ glFlush();
+
+ glReadPixels(0, 0, screen->w, screen->h, GL_RGBA, GL_UNSIGNED_BYTE, &pixels.front());
+
+ glPixelStorei(GL_PACK_ALIGNMENT, pack_aligment);
+
+ }
+ else
+ {
+ SDL_Rect* viewport = nullptr;
+ SDL_GetClipRect(screen, viewport);
+ SDL_LockSurface(screen);
+ SDL_ClearError();
+ if((result = SDL_RenderReadPixels(renderer_, viewport, SDL_PIXELFORMAT_RGBA32, &pixels.front(), pixels.size() * sizeof(std::array))) != 0);
+ {
+ SDL_Log("Couldn't read screen: %s\n", SDL_GetError());
+ pixels.clear();
+ }
+ SDL_UnlockSurface(screen);
+ }
+
+ if(!pixels.empty())
+ {
+ /* apply gamma ramp */
+ std::array r, g, b;
+ SDL_GetWindowGammaRamp(window_, r.begin(), g.begin(), b.begin());
+ for (auto & p : pixels)
+ p = { (uint8_t)(r[p[0]] >> 8), (uint8_t)(g[p[1]] >> 8), (uint8_t)(b[p[2]] >> 8), p[3] };
+
+ ChasmReverse::WriteTGA(screen->w, screen->h, &pixels.front().front(), nullptr, dst.c_str());
+ if(!exists(dst))
+ {
+ Log::Warning("Couldn't write screenshot: ", dst, " - ", strerror(errno));
+ result = -1;
+ }
+ pixels.clear();
+
+ }
+
+ }
+ SDL_ClearError();
+ SDL_FreeSurface(screen);
+ return result == 0 ? true : false;
+}
+
+}// namespace PanzerChasm
diff --git a/src/system_window.hpp b/src/system_window.hpp
index 6678f8dd..dad28041 100644
--- a/src/system_window.hpp
+++ b/src/system_window.hpp
@@ -1,5 +1,6 @@
#pragma once
#include
+#include
#include
#include "fwd.hpp"
@@ -44,7 +45,7 @@ class SystemWindow final
void GetInput( SystemEvents& out_events );
void GetInputState( InputState& out_input_state );
void CaptureMouse( bool need_capture );
-
+ bool CaptureScreen( const std::filesystem::path& file = "shot" ) const;
private:
struct PixelColorsOrder
{
@@ -64,6 +65,7 @@ class SystemWindow final
Size2 viewport_size_; // Inner viewport size, not system window size.
SDL_Window* window_= nullptr;
+ SDL_Renderer* renderer_= nullptr;
SDL_GLContext gl_context_= nullptr; // If not null - current mode is OpenGL, else - software.
bool use_gl_context_for_software_renderer_= false;
unsigned int software_renderer_gl_texture_= ~0u;
diff --git a/src/vfs.cpp b/src/vfs.cpp
index 3f55f9f1..b99238e7 100644
--- a/src/vfs.cpp
+++ b/src/vfs.cpp
@@ -1,209 +1,231 @@
-#include
-#include
-#include
-
-#include "common/files.hpp"
-using namespace ChasmReverse;
-
-#include "log.hpp"
-
#include "vfs.hpp"
-namespace PanzerChasm
-{
+namespace PanzerChasm {
-#pragma pack(push, 1)
-struct FileInfoPacked
-{
- unsigned char name_length;
- char name[ 12 ]; // without end null
- unsigned int size;
- unsigned int offset;
-};
-#pragma pack(pop)
-
-// Own replacement for nonstandard strncasecmp
-static bool StringEquals( const char* const s0, const char* const s1, const unsigned int max_length )
-{
- unsigned int i= 0;
- while( s0[i] != '\0' && s1[i] != '\0' && i < max_length )
+ int Vfs::SupportedFormats() const
{
- if( std::tolower( s0[i] ) != std::tolower( s1[i] ) )
- return false;
- i++;
- }
-
- return i == max_length || s0[i] == s1[i];
-}
-
-static std::string ToUpper( const std::string& s )
-{
- std::string r= s;
- for( char& c : r )
- c= std::toupper(c);
- return r;
-}
+ if( PHYSFS_isInit() == 0 )
+ {
+ if( PHYSFS_init( "" ) == 0 )
+ {
+ Log::FatalError( "PHYSFS_init() failed!\n reason %s.\n", PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode() ) );
+ std::error_code ec;
+ throw std::filesystem::filesystem_error(std::string( "Could not initialize PhysFS!" ), ec);
+ }
+ }
-static const char* ExtractFileName( const char* const file_path )
-{
- const char* file_name_pos= file_path;
+ const PHYSFS_ArchiveInfo **rc = PHYSFS_supportedArchiveTypes();
+ const PHYSFS_ArchiveInfo **i;
+ int supported = DIR;
- const char* str= file_path;
- while( *str != '\0' )
- {
- if( *str == '/' || *str == '\\' )
- file_name_pos= str + 1u;
-
- str++;
+ if( *rc == nullptr )
+ printf( "PHYSFS BIN/TAR format not supported!\n" );
+ else
+ {
+ for(i = rc; *i != nullptr; i++)
+ {
+ if( memcmp((*i)->extension, "TAR", 3) == 0 )
+ supported |= TAR;
+ if( memcmp((*i)->extension, "BIN", 3) == 0 )
+ supported |= CSM;
+ }
+ }
+ return supported;
}
- return file_name_pos;
-}
-
-static std::string PrepareAddonPath( const char* const addon_path )
-{
- if( addon_path == nullptr )
- return "";
-
- std::string result= addon_path;
- if( !result.empty() )
+ int Vfs::Type( const std::filesystem::path& path ) const
{
- if( !( result.back() == '/' || result.back() == '\\' ) )
- result.push_back( '/' );
+ static const char* CSM_MAGIC = "CSid";
+ static const char* TAR_MAGIC = "ustar "; /* 7 chars and a null */
+ static const ssize_t TAR_MAGIC_OFFSET = 257;
+ static const ssize_t TAR_BLOCK_SIZE = 512;
+ static const ssize_t CSM_BLOCK_SIZE = 4;
+ int res = NIL;
+ if( !path.empty() )
+ {
+ if( !std::filesystem::is_directory( path ) )
+ {
+ char buf[TAR_BLOCK_SIZE + 1] = { '\0' };
+ std::ifstream is( path, std::ios_base::binary );
+ is.read( buf, TAR_BLOCK_SIZE );
+ if( is.tellg() == TAR_BLOCK_SIZE )
+ {
+ if( strncmp( buf, CSM_MAGIC, CSM_BLOCK_SIZE ) == 0 )
+ {
+ res = CSM;
+ }
+ else if( strncmp(&buf[TAR_MAGIC_OFFSET], TAR_MAGIC, CSM_BLOCK_SIZE + 1 ) == 0 )
+ res = TAR;
+ }
+ is.close();
+ }
+ else
+ {
+ res = DIR;
+ }
+ }
+ return res;
}
- return result;
-}
-Vfs::VurtualFileName::VurtualFileName( const char* const in_text )
-{
- size_t i= 0u;
- while( i < sizeof(text) && in_text[i] != '\0' )
+ Vfs::Vfs( const std::filesystem::path& archive_file, const std::filesystem::path& addon_path )
{
- text[i]= in_text[i];
- ++i;
- }
+ archive_.path = archive_file;
+ archive_.type = (enum flags)Type( archive_.path );
+ addon_.path = addon_path;
+ addon_.type = (enum flags)Type( addon_.path );
+ formats_ = SupportedFormats();
- if( i < sizeof(text) )
- text[i]= '\0';
-}
-
-Vfs::VurtualFileName::VurtualFileName( const char* const in_text, const size_t size )
-{
- std::memcpy( &text, in_text, std::min( size, sizeof(text) ) );
- if( size < sizeof(text) )
- text[size]= '\0';
-}
-
-bool Vfs::VurtualFileName::operator==( const VurtualFileName& other ) const
-{
- return StringEquals( this->text, other.text, sizeof(text) );
-}
-
-size_t Vfs::VurtualFileNameHasher::operator()( const VurtualFileName& name ) const
-{
- // djb2 hash
- size_t hash= 5381u;
- for( size_t i= 0u; i < sizeof(name.text) && name.text[i] != '\0'; ++i )
- hash= hash * 33u + size_t(std::tolower(name.text[i]));
-
- return hash;
-}
-
-Vfs::Vfs(
- const char* archive_file_name,
- const char* const addon_path )
- : archive_file_( std::fopen( archive_file_name, "rb" ) )
- , addon_path_( PrepareAddonPath( addon_path ) )
-{
- if( archive_file_ == nullptr )
- {
- Log::FatalError( "Could not open file \"", archive_file_name, "\"" );
- return;
- }
+ if( !archive_.path.empty() )
+ {
+ if( (PHYSFS_mount( archive_.path.c_str(), "/", 0 ) == 0) )
+ {
+ Log::FatalError( "Could not open archive file \"", archive_.path, "\"" );
+ PHYSFS_deinit();
+ std::error_code ec;
+ throw std::filesystem::filesystem_error( std::string( "Could not initialize PhysFS!" ), ec );
+ }
+ Log::Info( archive_.type == TAR ? "TAR" : "BIN", " ", archive_.path );
+ }
- char header[4];
- FileRead( archive_file_, header, sizeof(header) );
- if( std::strncmp( header, "CSid", sizeof(header) ) != 0 )
- {
- Log::FatalError( "File \"", archive_file_name, "\" is not \"Chasm: The Rift\" archive" );
- return;
+ if( !addon_.path.empty() )
+ {
+ if( ( PHYSFS_mount( addon_.path.c_str(), "/", 0 ) == 0) )
+ {
+ Log::FatalError( "Could not open addon path \"", addon_.path, "\"" );
+ PHYSFS_deinit();
+ std::error_code ec;
+ throw std::filesystem::filesystem_error( std::string( "Could not initialize PhysFS!" ), ec);
+ }
+ Log::Info( addon_.type == DIR ? "DIR" : "NIL", " ", addon_.path );
+ }
}
- unsigned short files_in_archive_count;
- FileRead( archive_file_, &files_in_archive_count, sizeof(files_in_archive_count) );
-
- std::vector files_info_packed( files_in_archive_count );
- FileRead( archive_file_, files_info_packed.data(), files_info_packed.size() * sizeof(FileInfoPacked ) );
-
- virtual_files_.reserve( files_in_archive_count );
-
- for( const FileInfoPacked& file_info_packed : files_info_packed )
- {
- VirtualFile file;
- std::memcpy( &file.size , &file_info_packed.size , sizeof(unsigned int) );
- std::memcpy( &file.offset, &file_info_packed.offset, sizeof(unsigned int) );
-
- virtual_files_[ VurtualFileName( file_info_packed.name, file_info_packed.name_length ) ]= file;
- }
-}
-
-Vfs::~Vfs()
-{
- if( archive_file_ != nullptr )
- std::fclose( archive_file_ );
-}
-
-Vfs::FileContent Vfs::ReadFile( const char* const file_path ) const
-{
- FileContent result;
- ReadFile( file_path, result );
- return result;
-}
-
-void Vfs::ReadFile( const char* const file_path, FileContent& out_file_content ) const
-{
- const char* const file_name= ExtractFileName( file_path );
- if( file_name[0] == '\0' )
+ Vfs::~Vfs()
{
- // Do not load files with empty path.
- out_file_content.clear();
- return;
+ PHYSFS_deinit();
}
- // Try read from real file system.
- if( !addon_path_.empty() )
+ Vfs::FileContent Vfs::ReadFile( const std::filesystem::path& file_path ) const
{
- std::string fs_file_path= addon_path_ + ToUpper(file_path); // Use ToUpper, because files in addons are in upper case.
- std::replace(fs_file_path.begin(), fs_file_path.end(), '\\', '/' ); // Change shitty DOS/Windows path separators to universal windows/unix separators.
+ FileContent result;
+ ReadFile( file_path, result );
+ return result;
+ }
- std::FILE* const fs_file= std::fopen( fs_file_path.c_str(), "rb" );
+ void Vfs::ReadFile( const std::filesystem::path& file_path, FileContent& out_file_content ) const
+ {
+ std::string file_path_str = file_path.native();
+ std::replace(file_path_str.begin(), file_path_str.end(), '\\', '/');
+ std::filesystem::path loc( ToUpper( file_path_str ) );
- if( fs_file != nullptr )
+ if( !loc.empty() )
{
- std::fseek( fs_file, 0, SEEK_END );
- const unsigned int file_size= std::ftell( fs_file );
- std::fseek( fs_file, 0, SEEK_SET );
-
- out_file_content.resize( file_size );
- FileRead( fs_file, out_file_content.data(), file_size );
-
- std::fclose( fs_file );
- return;
+ std::error_code ec;
+ if( PHYSFS_isInit() == 0 )
+ {
+ throw std::filesystem::filesystem_error( std::string( "Could not initialize PhysFS!" ), ec );
+ }
+
+ if( !addon_.path.empty() && addon_.type == DIR )
+ {
+ PHYSFS_File *f = PHYSFS_openRead( loc.c_str() );
+ if( f != nullptr )
+ {
+ PHYSFS_sint64 file_size= PHYSFS_fileLength( f );
+ out_file_content.resize( file_size );
+ PHYSFS_sint64 read = PHYSFS_readBytes( f, out_file_content.data(), out_file_content.size() );
+ if( read == out_file_content.size() )
+ {
+ PHYSFS_flush( f );
+ PHYSFS_close( f );
+ return;
+ }
+ out_file_content.clear();
+ }
+ PHYSFS_close(f);
+ }
+
+ if( !archive_.path.empty() && archive_.type == CSM )
+ {
+ PHYSFS_File *f = PHYSFS_openRead( loc.filename().c_str() );
+ if( f != nullptr )
+ {
+ PHYSFS_sint64 file_size = PHYSFS_fileLength( f );
+ out_file_content.resize( file_size );
+ PHYSFS_sint64 read = PHYSFS_readBytes( f, out_file_content.data(), out_file_content.size() );
+ if( read == out_file_content.size() )
+ {
+ PHYSFS_flush( f );
+ PHYSFS_close( f );
+ return;
+ }
+ out_file_content.clear();
+ }
+ PHYSFS_close( f );
+ }
+
+ if( !archive_.path.empty() && archive_.type == TAR )
+ {
+ loc = ToLower( loc.native() );
+ if( loc.filename().extension() == ".raw" || loc.filename().extension() == ".sfx" || loc.filename().extension() == ".pcm" )
+ {
+ if( !(loc.filename() == "aproc.sfx" || loc.filename() == "wswap.sfx" || loc.filename() == "thund3.raw" || loc.filename() == "volc.raw" || loc.filename() == "step02.raw" ) )
+ loc = "sound" / loc.filename().replace_extension( ".wav" );
+ }
+ else if( loc.filename().extension() == ".wav" )
+ loc = "sound" / loc.filename();
+ else if( loc.filename().extension() == ".cel" )
+ loc = "texture" / loc.filename();
+ else if( loc.parent_path() == "models" )
+ loc = "model" / loc.filename();
+ else if( loc.filename().extension() == ".car" )
+ loc = "monster" / loc.filename();
+ else if( loc.parent_path() == "ani" || loc.parent_path() == "ani/weapon" || loc.parent_path() == "ani/weapon" )
+ loc = "model" / loc.filename();
+ else if( loc.stem() == "floors" || loc.stem() == "resource" || loc.stem() == "process" || loc.stem() == "map" || loc.stem() == "script" )
+ loc = "map" / loc.filename();
+ else if( loc.filename().extension() == ".obj" )
+ {
+ if( loc.parent_path() != "monster" )
+ loc = "sprite_effects" / loc.filename();
+ }
+ else if( loc.filename().extension() == ".ani" )
+ loc = "model" / loc.filename();
+ if( loc.filename().extension() == ".3o" )
+ loc = "model" / loc.filename();
+ else if( loc.filename().extension() == ".gltf" )
+ {
+ if( loc.parent_path() != "monster" )
+ loc = "model" / loc.filename();
+ }
+
+ if( !loc.filename().has_extension() )
+ {
+ std::filesystem::path oldloc = loc;
+ if( loc.filename() == "faust" )
+ loc = "monster" / loc.filename().replace_filename( "faust1.car" );
+ if( loc.filename() == "vent1" )
+ loc = "model" / loc.filename().replace_filename( "vent1.gltf" );
+ Log::Warning( "Warning: ", oldloc, " -> ", loc, " renamed!" );
+ }
+
+ char* path = (char*)loc.c_str();
+ PHYSFS_File *f = PHYSFS_openRead( ( path ) );
+ if( f != nullptr )
+ {
+ PHYSFS_sint64 file_size = PHYSFS_fileLength( f );
+ out_file_content.resize( file_size );
+ PHYSFS_sint64 read = PHYSFS_readBytes( f, out_file_content.data(), out_file_content.size() );
+ if( read == out_file_content.size() )
+ {
+ PHYSFS_flush( f );
+ PHYSFS_close( f );
+ return;
+ }
+ out_file_content.clear();
+ }
+ PHYSFS_close( f );
+ }
}
}
-
- const auto it= virtual_files_.find( VurtualFileName( file_name ) );
- if( it != virtual_files_.end() )
- {
- const VirtualFile& file= it->second;
- out_file_content.resize( file.size );
- std::fseek( archive_file_, file.offset, SEEK_SET );
- FileRead( archive_file_, out_file_content.data(), out_file_content.size() );
-
- return;
- }
-
- out_file_content.clear();
-}
-
-} // namespace PanzerChasm
+};
diff --git a/src/vfs.hpp b/src/vfs.hpp
index 4c6e7289..1e133bdd 100644
--- a/src/vfs.hpp
+++ b/src/vfs.hpp
@@ -4,50 +4,52 @@
#include
#include
#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include