initial commit where rendering still works
This commit is contained in:
commit
8e9a80caf0
8
.gitignore
vendored
Normal file
8
.gitignore
vendored
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
gameenv/data/maps
|
||||||
|
gameenv/data/textures
|
||||||
|
deps
|
||||||
|
.vs
|
||||||
|
x64
|
||||||
|
*/x64
|
||||||
|
x86
|
||||||
|
*/x86
|
5
README.md
Normal file
5
README.md
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
# mpfw
|
||||||
|
## focusing on complete id control
|
||||||
|
|
||||||
|
## Supported file types
|
||||||
|
- BSP29 (Quake maps)
|
5
gameenv/data/cfg/startup.cfg
Normal file
5
gameenv/data/cfg/startup.cfg
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
echo "MPFW Quake"
|
||||||
|
echo "Test Startup Script"
|
||||||
|
|
||||||
|
mode 1
|
||||||
|
map "data/maps/fullquake/start.bsp"
|
91
gameenv/data/fonts/RobotoMono/LICENSE.txt
Normal file
91
gameenv/data/fonts/RobotoMono/LICENSE.txt
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||||
|
This license is copied below, and is also available with a FAQ at:
|
||||||
|
https://openfontlicense.org
|
||||||
|
|
||||||
|
|
||||||
|
-----------------------------------------------------------
|
||||||
|
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||||
|
-----------------------------------------------------------
|
||||||
|
|
||||||
|
PREAMBLE
|
||||||
|
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||||
|
development of collaborative font projects, to support the font creation
|
||||||
|
efforts of academic and linguistic communities, and to provide a free and
|
||||||
|
open framework in which fonts may be shared and improved in partnership
|
||||||
|
with others.
|
||||||
|
|
||||||
|
The OFL allows the licensed fonts to be used, studied, modified and
|
||||||
|
redistributed freely as long as they are not sold by themselves. The
|
||||||
|
fonts, including any derivative works, can be bundled, embedded,
|
||||||
|
redistributed and/or sold with any software provided that any reserved
|
||||||
|
names are not used by derivative works. The fonts and derivatives,
|
||||||
|
however, cannot be released under any other type of license. The
|
||||||
|
requirement for fonts to remain under this license does not apply
|
||||||
|
to any document created using the fonts or their derivatives.
|
||||||
|
|
||||||
|
DEFINITIONS
|
||||||
|
"Font Software" refers to the set of files released by the Copyright
|
||||||
|
Holder(s) under this license and clearly marked as such. This may
|
||||||
|
include source files, build scripts and documentation.
|
||||||
|
|
||||||
|
"Reserved Font Name" refers to any names specified as such after the
|
||||||
|
copyright statement(s).
|
||||||
|
|
||||||
|
"Original Version" refers to the collection of Font Software components as
|
||||||
|
distributed by the Copyright Holder(s).
|
||||||
|
|
||||||
|
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||||
|
or substituting -- in part or in whole -- any of the components of the
|
||||||
|
Original Version, by changing formats or by porting the Font Software to a
|
||||||
|
new environment.
|
||||||
|
|
||||||
|
"Author" refers to any designer, engineer, programmer, technical
|
||||||
|
writer or other person who contributed to the Font Software.
|
||||||
|
|
||||||
|
PERMISSION & CONDITIONS
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||||
|
redistribute, and sell modified and unmodified copies of the Font
|
||||||
|
Software, subject to the following conditions:
|
||||||
|
|
||||||
|
1) Neither the Font Software nor any of its individual components,
|
||||||
|
in Original or Modified Versions, may be sold by itself.
|
||||||
|
|
||||||
|
2) Original or Modified Versions of the Font Software may be bundled,
|
||||||
|
redistributed and/or sold with any software, provided that each copy
|
||||||
|
contains the above copyright notice and this license. These can be
|
||||||
|
included either as stand-alone text files, human-readable headers or
|
||||||
|
in the appropriate machine-readable metadata fields within text or
|
||||||
|
binary files as long as those fields can be easily viewed by the user.
|
||||||
|
|
||||||
|
3) No Modified Version of the Font Software may use the Reserved Font
|
||||||
|
Name(s) unless explicit written permission is granted by the corresponding
|
||||||
|
Copyright Holder. This restriction only applies to the primary font name as
|
||||||
|
presented to the users.
|
||||||
|
|
||||||
|
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||||
|
Software shall not be used to promote, endorse or advertise any
|
||||||
|
Modified Version, except to acknowledge the contribution(s) of the
|
||||||
|
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||||
|
permission.
|
||||||
|
|
||||||
|
5) The Font Software, modified or unmodified, in part or in whole,
|
||||||
|
must be distributed entirely under this license, and must not be
|
||||||
|
distributed under any other license. The requirement for fonts to
|
||||||
|
remain under this license does not apply to any document created
|
||||||
|
using the Font Software.
|
||||||
|
|
||||||
|
TERMINATION
|
||||||
|
This license becomes null and void if any of the above conditions are
|
||||||
|
not met.
|
||||||
|
|
||||||
|
DISCLAIMER
|
||||||
|
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||||
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||||
|
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||||
|
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||||
|
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||||
|
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||||
|
OTHER DEALINGS IN THE FONT SOFTWARE.
|
BIN
gameenv/data/fonts/RobotoMono/RobotoMono-Bold.ttf
Normal file
BIN
gameenv/data/fonts/RobotoMono/RobotoMono-Bold.ttf
Normal file
Binary file not shown.
BIN
gameenv/data/fonts/RobotoMono/RobotoMono-BoldItalic.ttf
Normal file
BIN
gameenv/data/fonts/RobotoMono/RobotoMono-BoldItalic.ttf
Normal file
Binary file not shown.
BIN
gameenv/data/fonts/RobotoMono/RobotoMono-ExtraLight.ttf
Normal file
BIN
gameenv/data/fonts/RobotoMono/RobotoMono-ExtraLight.ttf
Normal file
Binary file not shown.
BIN
gameenv/data/fonts/RobotoMono/RobotoMono-ExtraLightItalic.ttf
Normal file
BIN
gameenv/data/fonts/RobotoMono/RobotoMono-ExtraLightItalic.ttf
Normal file
Binary file not shown.
BIN
gameenv/data/fonts/RobotoMono/RobotoMono-Italic.ttf
Normal file
BIN
gameenv/data/fonts/RobotoMono/RobotoMono-Italic.ttf
Normal file
Binary file not shown.
BIN
gameenv/data/fonts/RobotoMono/RobotoMono-Light.ttf
Normal file
BIN
gameenv/data/fonts/RobotoMono/RobotoMono-Light.ttf
Normal file
Binary file not shown.
BIN
gameenv/data/fonts/RobotoMono/RobotoMono-LightItalic.ttf
Normal file
BIN
gameenv/data/fonts/RobotoMono/RobotoMono-LightItalic.ttf
Normal file
Binary file not shown.
BIN
gameenv/data/fonts/RobotoMono/RobotoMono-Medium.ttf
Normal file
BIN
gameenv/data/fonts/RobotoMono/RobotoMono-Medium.ttf
Normal file
Binary file not shown.
BIN
gameenv/data/fonts/RobotoMono/RobotoMono-MediumItalic.ttf
Normal file
BIN
gameenv/data/fonts/RobotoMono/RobotoMono-MediumItalic.ttf
Normal file
Binary file not shown.
BIN
gameenv/data/fonts/RobotoMono/RobotoMono-Regular.ttf
Normal file
BIN
gameenv/data/fonts/RobotoMono/RobotoMono-Regular.ttf
Normal file
Binary file not shown.
BIN
gameenv/data/fonts/RobotoMono/RobotoMono-SemiBold.ttf
Normal file
BIN
gameenv/data/fonts/RobotoMono/RobotoMono-SemiBold.ttf
Normal file
Binary file not shown.
BIN
gameenv/data/fonts/RobotoMono/RobotoMono-SemiBoldItalic.ttf
Normal file
BIN
gameenv/data/fonts/RobotoMono/RobotoMono-SemiBoldItalic.ttf
Normal file
Binary file not shown.
BIN
gameenv/data/fonts/RobotoMono/RobotoMono-Thin.ttf
Normal file
BIN
gameenv/data/fonts/RobotoMono/RobotoMono-Thin.ttf
Normal file
Binary file not shown.
BIN
gameenv/data/fonts/RobotoMono/RobotoMono-ThinItalic.ttf
Normal file
BIN
gameenv/data/fonts/RobotoMono/RobotoMono-ThinItalic.ttf
Normal file
Binary file not shown.
BIN
gameenv/data/palette.lmp
Normal file
BIN
gameenv/data/palette.lmp
Normal file
Binary file not shown.
41
mpfw.sln
Normal file
41
mpfw.sln
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
|
||||||
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
|
# Visual Studio Version 17
|
||||||
|
VisualStudioVersion = 17.12.35527.113
|
||||||
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
|
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "mpfw", "mpfw\mpfw.vcxproj", "{FB36E71A-46AB-4ECE-9438-E683C4208FB1}"
|
||||||
|
EndProject
|
||||||
|
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "mpfw_server", "mpfw_server\mpfw_server.vcxproj", "{67CEAC9D-B0F3-4AB3-B3FF-0A9ADAD98E45}"
|
||||||
|
EndProject
|
||||||
|
Global
|
||||||
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
Debug|x64 = Debug|x64
|
||||||
|
Debug|x86 = Debug|x86
|
||||||
|
Release|x64 = Release|x64
|
||||||
|
Release|x86 = Release|x86
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
|
{FB36E71A-46AB-4ECE-9438-E683C4208FB1}.Debug|x64.ActiveCfg = Debug|x64
|
||||||
|
{FB36E71A-46AB-4ECE-9438-E683C4208FB1}.Debug|x64.Build.0 = Debug|x64
|
||||||
|
{FB36E71A-46AB-4ECE-9438-E683C4208FB1}.Debug|x86.ActiveCfg = Debug|Win32
|
||||||
|
{FB36E71A-46AB-4ECE-9438-E683C4208FB1}.Debug|x86.Build.0 = Debug|Win32
|
||||||
|
{FB36E71A-46AB-4ECE-9438-E683C4208FB1}.Release|x64.ActiveCfg = Release|x64
|
||||||
|
{FB36E71A-46AB-4ECE-9438-E683C4208FB1}.Release|x64.Build.0 = Release|x64
|
||||||
|
{FB36E71A-46AB-4ECE-9438-E683C4208FB1}.Release|x86.ActiveCfg = Release|Win32
|
||||||
|
{FB36E71A-46AB-4ECE-9438-E683C4208FB1}.Release|x86.Build.0 = Release|Win32
|
||||||
|
{67CEAC9D-B0F3-4AB3-B3FF-0A9ADAD98E45}.Debug|x64.ActiveCfg = Debug|x64
|
||||||
|
{67CEAC9D-B0F3-4AB3-B3FF-0A9ADAD98E45}.Debug|x64.Build.0 = Debug|x64
|
||||||
|
{67CEAC9D-B0F3-4AB3-B3FF-0A9ADAD98E45}.Debug|x86.ActiveCfg = Debug|Win32
|
||||||
|
{67CEAC9D-B0F3-4AB3-B3FF-0A9ADAD98E45}.Debug|x86.Build.0 = Debug|Win32
|
||||||
|
{67CEAC9D-B0F3-4AB3-B3FF-0A9ADAD98E45}.Release|x64.ActiveCfg = Release|x64
|
||||||
|
{67CEAC9D-B0F3-4AB3-B3FF-0A9ADAD98E45}.Release|x64.Build.0 = Release|x64
|
||||||
|
{67CEAC9D-B0F3-4AB3-B3FF-0A9ADAD98E45}.Release|x86.ActiveCfg = Release|Win32
|
||||||
|
{67CEAC9D-B0F3-4AB3-B3FF-0A9ADAD98E45}.Release|x86.Build.0 = Release|Win32
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
|
HideSolutionNode = FALSE
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
|
SolutionGuid = {D438E333-35BA-4815-92C4-C0C289E3CD9B}
|
||||||
|
EndGlobalSection
|
||||||
|
EndGlobal
|
16
mpfw/MPFW_CVars.cpp
Normal file
16
mpfw/MPFW_CVars.cpp
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
#include "MPFW_CVars.h"
|
||||||
|
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
|
||||||
|
std::variant<std::string, int> MPFW::CVar::get() {
|
||||||
|
try {
|
||||||
|
int retval = std::stoi(value);
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
|
catch(...){
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
17
mpfw/MPFW_CVars.h
Normal file
17
mpfw/MPFW_CVars.h
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <iostream>
|
||||||
|
#include <map>
|
||||||
|
#include <variant>
|
||||||
|
|
||||||
|
namespace MPFW {
|
||||||
|
class CVar {
|
||||||
|
public:
|
||||||
|
std::string value;
|
||||||
|
std::variant<std::string, int> get();
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
using CVarMap = std::map<std::string, CVar>;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
175
mpfw/MPFW_Console.cpp
Normal file
175
mpfw/MPFW_Console.cpp
Normal file
@ -0,0 +1,175 @@
|
|||||||
|
#include "MPFW_Console.h"
|
||||||
|
#include <string>
|
||||||
|
#include <fstream>
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
|
|
||||||
|
MPFW_ConsoleCommand(echo) {
|
||||||
|
for (int i = 0; i < cmd.size(); i++) {
|
||||||
|
*logStr += std::format("{} ", cmd[i]);
|
||||||
|
}
|
||||||
|
*logStr += "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
MPFW_ConsoleCommand(quit) {
|
||||||
|
exit(0); // some day there'll be proper unloading :3
|
||||||
|
}
|
||||||
|
|
||||||
|
MPFW_ConsoleCommand(mode) {
|
||||||
|
if (cmd.size() != 1) {
|
||||||
|
*logStr += "'mode' command usage:\n mode <0/1>\n\nModes:\n 0 - MPFW (default)\n 1 - Quake\n";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
try {
|
||||||
|
int newMode = std::stoi(cmd[0]);
|
||||||
|
if (newMode > 1 || newMode < 0) throw std::out_of_range("not a mode");
|
||||||
|
ctx->mode = (MPFW::Console::OperationMode)newMode;
|
||||||
|
*logStr += "Mode is set to '" + std::to_string(newMode) + "'\n";
|
||||||
|
}
|
||||||
|
catch(...){
|
||||||
|
*logStr += "'mode' command usage:\n mode <0/1>\n\nModes:\n 0 - MPFW (default)\n 1 - Quake\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MPFW_ConsoleCommand(map_quake) {
|
||||||
|
|
||||||
|
if (cmd.size() != 1) {
|
||||||
|
*logStr += "'map' command usage:\n map <path>\n";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
try {
|
||||||
|
ctx->mapQuake->LoadBSPMap(cmd[0]);
|
||||||
|
*logStr += std::format("{}", ctx->mapQuake->data);
|
||||||
|
}
|
||||||
|
catch (...) {
|
||||||
|
*logStr += "Couldn't load map '" + cmd[0] + "'\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MPFW_ConsoleCommand(map_mpfw) {
|
||||||
|
|
||||||
|
*logStr += "Mode MPFW has no \"map\" command because it has no available map format.\n\nUse one of these modes instead:\n - 'mode 1' : QUAKE\n\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
MPFW_ConsoleCommand(set) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
MPFW::Console::CommandHandler::CommandHandler(CommandHandlerResources* c)
|
||||||
|
{
|
||||||
|
|
||||||
|
chr = c;
|
||||||
|
|
||||||
|
#pragma region
|
||||||
|
|
||||||
|
|
||||||
|
// System commands are defined manually à la Jason Thor Hall™
|
||||||
|
// but that's to make absolutely f*cking sure that these
|
||||||
|
// elemental commands work. less important commands will be
|
||||||
|
// defined with a definition instantiator or some random shit
|
||||||
|
|
||||||
|
CommandID mpfwEcho = { MPFW, "echo" };
|
||||||
|
CommandID quakeEcho = { QUAKE, "echo" };
|
||||||
|
functionMap[mpfwEcho] = echo;
|
||||||
|
functionMap[quakeEcho] = echo;
|
||||||
|
|
||||||
|
|
||||||
|
CommandID mpfwQuit = { MPFW, "quit" };
|
||||||
|
CommandID quakeQuit = { QUAKE, "quit" };
|
||||||
|
functionMap[mpfwQuit] = quit;
|
||||||
|
functionMap[quakeQuit] = quit;
|
||||||
|
|
||||||
|
|
||||||
|
CommandID mpfwMode = { MPFW, "mode" };
|
||||||
|
CommandID quakeMode = { QUAKE, "mode" };
|
||||||
|
functionMap[mpfwMode] = mode;
|
||||||
|
functionMap[quakeMode] = mode;
|
||||||
|
|
||||||
|
CommandID mpfwMap = { MPFW, "map" };
|
||||||
|
functionMap[mpfwMap] = map_mpfw;
|
||||||
|
|
||||||
|
CommandID quakeMap = { QUAKE, "map" };
|
||||||
|
functionMap[quakeMap] = map_quake;
|
||||||
|
|
||||||
|
#pragma endregion System Commands
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> MPFW::Console::CommandHandler::parseCommand(std::string s)
|
||||||
|
{
|
||||||
|
|
||||||
|
|
||||||
|
bool inQString = false;
|
||||||
|
|
||||||
|
|
||||||
|
std::vector<std::string> retval;
|
||||||
|
|
||||||
|
std::string cstr; // current string
|
||||||
|
for (int i = 0; i < s.size(); i++) {
|
||||||
|
if (s[i] == '"') {
|
||||||
|
inQString = !inQString;
|
||||||
|
if (!inQString) {
|
||||||
|
retval.push_back(cstr);
|
||||||
|
cstr = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (!inQString && s[i] == ' ') {
|
||||||
|
retval.push_back(cstr);
|
||||||
|
cstr = "";
|
||||||
|
}
|
||||||
|
else cstr += s[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(cstr != "") retval.push_back(cstr);
|
||||||
|
|
||||||
|
return retval;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void MPFW::Console::CommandHandler::Run(std::string command, bool verbose)
|
||||||
|
{
|
||||||
|
if(verbose) logStr += "\n" + std::string(chr->mode == QUAKE ? "QUAKE" : "MPFW") + "] " + command + "\n";
|
||||||
|
std::vector<std::string> cmdParseResult = parseCommand(command);
|
||||||
|
CommandID id;
|
||||||
|
if (cmdParseResult.size() > 0){
|
||||||
|
id.n = cmdParseResult[0];
|
||||||
|
cmdParseResult.erase(cmdParseResult.begin(), cmdParseResult.begin() + 1);
|
||||||
|
|
||||||
|
id.m = chr->mode;
|
||||||
|
|
||||||
|
if (functionMap.find(id) != functionMap.end()) {
|
||||||
|
functionMap[id](cmdParseResult, chr, &logStr);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
logStr += "Unknown command " + id.n + " in mode " + (chr->mode == QUAKE ? "QUAKE" : "MPFW") + "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void MPFW::Console::CommandHandler::RunScript(std::string path)
|
||||||
|
{
|
||||||
|
std::ifstream i(path);
|
||||||
|
if (i.good() && i.is_open()) {
|
||||||
|
std::string line;
|
||||||
|
|
||||||
|
while (std::getline(i, line)) {
|
||||||
|
Run(line, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
throw std::runtime_error("Can't run called script " + path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MPFW::Console::CommandHandler::~CommandHandler()
|
||||||
|
{
|
||||||
|
}
|
84
mpfw/MPFW_Console.h
Normal file
84
mpfw/MPFW_Console.h
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <iostream>
|
||||||
|
#include <vector>
|
||||||
|
#include "MPFW_Quake.h"
|
||||||
|
#include <functional>
|
||||||
|
#include <map>
|
||||||
|
|
||||||
|
#define MPFW_ConsoleCommand(x) void x(std::vector<std::string> cmd, MPFW::Console::CommandHandlerResources* ctx, std::string* logStr)
|
||||||
|
|
||||||
|
namespace MPFW {
|
||||||
|
namespace Console {
|
||||||
|
|
||||||
|
enum OperationMode {
|
||||||
|
MPFW = 0,
|
||||||
|
QUAKE = 1
|
||||||
|
};
|
||||||
|
|
||||||
|
struct CommandHandlerResources {
|
||||||
|
Quake::Maps::MapFile* mapQuake;
|
||||||
|
OperationMode mode = MPFW;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
struct CommandID {
|
||||||
|
OperationMode m;
|
||||||
|
std::string n;
|
||||||
|
};
|
||||||
|
inline bool operator==(const CommandID& left, const CommandID& right) {
|
||||||
|
return left.m == right.m && left.n == right.n;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
namespace std
|
||||||
|
{
|
||||||
|
template<>
|
||||||
|
struct hash<MPFW::Console::CommandID>
|
||||||
|
{
|
||||||
|
typedef MPFW::Console::CommandID argument_type;
|
||||||
|
typedef std::size_t result_type;
|
||||||
|
result_type operator()(argument_type const& in) const
|
||||||
|
{
|
||||||
|
std::string t;
|
||||||
|
t = in.n + (in.m == MPFW::Console::QUAKE ? "QUAKE" : "MPFW");
|
||||||
|
std::hash<std::string> h;
|
||||||
|
|
||||||
|
return h(t);
|
||||||
|
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
namespace MPFW{
|
||||||
|
namespace Console{
|
||||||
|
|
||||||
|
class CommandHandler {
|
||||||
|
CommandHandlerResources* chr;
|
||||||
|
std::unordered_map<CommandID, std::function<void(std::vector<std::string>,CommandHandlerResources*, std::string*)>> functionMap;
|
||||||
|
public:
|
||||||
|
|
||||||
|
[[deprecated("Never initialize an empty command handler")]]
|
||||||
|
CommandHandler() { throw; }
|
||||||
|
|
||||||
|
CommandHandler(CommandHandlerResources* chr);
|
||||||
|
|
||||||
|
std::vector<std::string> parseCommand(std::string s);
|
||||||
|
|
||||||
|
std::string logStr;
|
||||||
|
void Run(std::string command, bool verbose = true);
|
||||||
|
void RunScript(std::string path);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
~CommandHandler();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
180
mpfw/MPFW_HL.cpp
Normal file
180
mpfw/MPFW_HL.cpp
Normal file
@ -0,0 +1,180 @@
|
|||||||
|
#include "MPFW_HL.h"
|
||||||
|
#include "MPFW_Utils.h"
|
||||||
|
#include <fstream>
|
||||||
|
#include <sstream>
|
||||||
|
#include <string>
|
||||||
|
#include <iostream>
|
||||||
|
#include <exception>
|
||||||
|
|
||||||
|
MPFW::HL::WAD::WAD()
|
||||||
|
{
|
||||||
|
std::cout << "Warning: Empty WAD instance created.\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
MPFW::HL::WAD::WAD(std::string path)
|
||||||
|
{
|
||||||
|
LoadWAD(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MPFW::HL::WAD::LoadWAD(std::string path)
|
||||||
|
{
|
||||||
|
loaded = false;
|
||||||
|
lumps.clear();
|
||||||
|
lumpCount = 0;
|
||||||
|
std::cout << "Loading file " << path << "...\n";
|
||||||
|
std::ifstream file(path, std::ios::binary);
|
||||||
|
std::string filedata;
|
||||||
|
std::stringstream filestream; filestream << file.rdbuf();
|
||||||
|
filedata = filestream.str();/*
|
||||||
|
if (
|
||||||
|
|
||||||
|
|
||||||
|
MPFW::Utils::Strings::IterativeComp(filedata, 0, "IWAD")) {
|
||||||
|
wadType = IWAD;
|
||||||
|
std::cout << "File is an IWAD\n";
|
||||||
|
}
|
||||||
|
else if (MPFW::Utils::Strings::IterativeComp(filedata, 0, "PWAD")) {
|
||||||
|
wadType = PWAD;
|
||||||
|
std::cout << "File is a PWAD\n";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
throw std::runtime_error("Invalid WAD file: Error at header start(IWAD/PWAD)\n");
|
||||||
|
}*/
|
||||||
|
lumpCount = MPFW::Utils::Strings::StringIndexToInteger_4b_le(filedata, 4);
|
||||||
|
|
||||||
|
std::cout << "File has " << lumpCount << " lumps\n";
|
||||||
|
int directoryPointer = MPFW::Utils::Strings::StringIndexToInteger_4b_le(filedata, 8);
|
||||||
|
|
||||||
|
std::string directory = MPFW::Utils::Strings::IterativeStringExcerpt(filedata, directoryPointer, 16 * lumpCount);
|
||||||
|
|
||||||
|
for (int i = 0; i < lumpCount; i++) {
|
||||||
|
Lump lump;
|
||||||
|
std::string lumpIndex = MPFW::Utils::Strings::IterativeStringExcerpt(directory, i * 16, 16);
|
||||||
|
int lumpPointer = MPFW::Utils::Strings::StringIndexToInteger_4b_le(lumpIndex, 0);
|
||||||
|
int lumpSize = MPFW::Utils::Strings::StringIndexToInteger_4b_le(lumpIndex, 4);
|
||||||
|
lump.name = "", lump.data = "";
|
||||||
|
for (int j = 0; j < 8; j++) {
|
||||||
|
if (lumpIndex.at(8 + j) != 0x0) lump.name += lumpIndex.at(8 + j);
|
||||||
|
}
|
||||||
|
|
||||||
|
lump.data = MPFW::Utils::Strings::IterativeStringExcerpt(filedata, lumpPointer, lumpSize);
|
||||||
|
|
||||||
|
lumps.push_back(lump);
|
||||||
|
|
||||||
|
std::cout << "(" << i << "/" << lumpCount << ")" << "Loaded lump \"" << lump.name << "\" of size " << lumpSize << "\n";
|
||||||
|
}
|
||||||
|
loaded = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
MPFW::HL::WAD::~WAD()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
MPFW::HL::Map::Map()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
MPFW::HL::Map::Map(std::string path)
|
||||||
|
{
|
||||||
|
Load(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MPFW::HL::Map::Load(std::string path)
|
||||||
|
{
|
||||||
|
std::ifstream in(path);
|
||||||
|
|
||||||
|
if (!in.is_open()) throw std::runtime_error("Cannot open file " + path + "\n");
|
||||||
|
|
||||||
|
int scope = 0;
|
||||||
|
|
||||||
|
|
||||||
|
std::string line;
|
||||||
|
GenericUnknownClass* parsedClass = new GenericUnknownClass();
|
||||||
|
WorldSpawnClass* worldSpawn = new WorldSpawnClass();
|
||||||
|
bool inWorldSpawn = false;
|
||||||
|
while (std::getline(in, line)) {
|
||||||
|
|
||||||
|
if (line == "{") {
|
||||||
|
scope++;
|
||||||
|
std::cout << "scope: " << scope << std::endl;
|
||||||
|
}
|
||||||
|
else if (line == "}") {
|
||||||
|
scope--;
|
||||||
|
std::cout << "scope: " << scope << std::endl;
|
||||||
|
if (scope == 0) {
|
||||||
|
if (inWorldSpawn) {
|
||||||
|
inWorldSpawn = false;
|
||||||
|
mapdata.push_back(worldSpawn);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
mapdata.push_back(parsedClass);
|
||||||
|
parsedClass = new GenericUnknownClass();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (line != "") {
|
||||||
|
if (scope == 1) {
|
||||||
|
std::string key = MPFW::Utils::Strings::IterativeStringExcerpt_delim(line, 1, '"');
|
||||||
|
std::string val = MPFW::Utils::Strings::IterativeStringExcerpt_delim(line, 4 + key.size(), '"');
|
||||||
|
std::cout << key << " : " << val << "\n";
|
||||||
|
if (key == "classname") {
|
||||||
|
if (val == "worldspawn") {
|
||||||
|
inWorldSpawn = true;
|
||||||
|
std::cout << "in world spawn\n";
|
||||||
|
worldSpawn->classname = "worldspawn";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
parsedClass->classname = val;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (!inWorldSpawn) parsedClass->fields[key] = val;
|
||||||
|
else {
|
||||||
|
if (key == "defaultteam") worldSpawn->defaultteam = std::stoi(val);
|
||||||
|
if (key == "newunit") worldSpawn->newunit = std::stoi(val);
|
||||||
|
if (key == "gametitle") worldSpawn->gametitle = std::stoi(val);
|
||||||
|
if (key == "startdark") worldSpawn->startdark = std::stoi(val);
|
||||||
|
if (key == "MaxRange") worldSpawn->maxrange = std::stoi(val);
|
||||||
|
if (key == "sounds") worldSpawn->sounds = std::stoi(val);
|
||||||
|
if (key == "mapversion") worldSpawn->mapversion = std::stoi(val);
|
||||||
|
if (key == "wad") worldSpawn->wad = val;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
else if (scope == 2) {
|
||||||
|
std::stringstream stream(line);
|
||||||
|
Plane plane;
|
||||||
|
std::string temp;
|
||||||
|
stream >>
|
||||||
|
temp >> plane.a.x >> plane.a.y >> plane.a.z >> temp >>
|
||||||
|
temp >> plane.b.x >> plane.b.y >> plane.b.z >> temp >>
|
||||||
|
temp >> plane.c.x >> plane.c.y >> plane.c.z >> temp >>
|
||||||
|
plane.texturename >>
|
||||||
|
temp >> plane.u.x >> plane.u.y >> plane.u.z >> plane.uOffset >> temp >>
|
||||||
|
temp >> plane.v.x >> plane.v.y >> plane.v.z >> plane.vOffset >> temp >>
|
||||||
|
plane.rotation >> plane.uScale >> plane.vScale;
|
||||||
|
|
||||||
|
worldSpawn->planes.push_back(plane);
|
||||||
|
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
throw std::out_of_range("Out of scope content");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (scope != 0) {
|
||||||
|
if (scope > 1) throw std::length_error("File " + path + " is visibly incomplete by scope arithmetics.\n");
|
||||||
|
else {
|
||||||
|
std::cerr << "[WARNING] File " + path + " is missing a final closing bracket.\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
in.close();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
MPFW::HL::Map::~Map()
|
||||||
|
{
|
||||||
|
}
|
85
mpfw/MPFW_HL.h
Normal file
85
mpfw/MPFW_HL.h
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <iostream>
|
||||||
|
#include <vector>
|
||||||
|
#include <raylib.h>
|
||||||
|
#include <map>
|
||||||
|
|
||||||
|
namespace MPFW {
|
||||||
|
namespace HL {
|
||||||
|
struct Lump {
|
||||||
|
std::string name, data;
|
||||||
|
};
|
||||||
|
|
||||||
|
class WAD { // WAD3 file format
|
||||||
|
std::vector<Lump> lumps;
|
||||||
|
public:
|
||||||
|
bool loaded = false;
|
||||||
|
int lumpCount;
|
||||||
|
WAD();
|
||||||
|
WAD(std::string path);
|
||||||
|
void LoadWAD(std::string path);
|
||||||
|
std::vector<Lump> getLumps() {
|
||||||
|
return lumps;
|
||||||
|
}
|
||||||
|
~WAD();
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Plane {
|
||||||
|
Vector3 a, b, c, u, v;
|
||||||
|
std::string texturename;
|
||||||
|
float rotation, uOffset, uScale, vOffset, vScale;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Brush {
|
||||||
|
std::vector<Plane> planes;
|
||||||
|
};
|
||||||
|
|
||||||
|
class GenericClass {
|
||||||
|
public:
|
||||||
|
std::string classname;
|
||||||
|
virtual void onCreate() = 0;
|
||||||
|
virtual void onUpdate() = 0;
|
||||||
|
virtual void onDestroy() = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
class GenericOriginedClass : public GenericClass {
|
||||||
|
public:
|
||||||
|
Vector3 origin;
|
||||||
|
virtual void onCreate() = 0;
|
||||||
|
virtual void onUpdate() = 0;
|
||||||
|
virtual void onDestroy() = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
class GenericUnknownClass : public GenericClass{
|
||||||
|
public:
|
||||||
|
std::map<std::string, std::string> fields;
|
||||||
|
void onCreate() {}
|
||||||
|
void onUpdate() {}
|
||||||
|
void onDestroy() {}
|
||||||
|
};
|
||||||
|
|
||||||
|
class WorldSpawnClass : public GenericClass {
|
||||||
|
public:
|
||||||
|
std::string wad;
|
||||||
|
int defaultteam, newunit, gametitle, startdark, maxrange, sounds, mapversion;
|
||||||
|
std::vector<Plane> planes;
|
||||||
|
void onCreate() {}
|
||||||
|
void onUpdate() {}
|
||||||
|
void onDestroy() {}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct __HLParseKeyVal {
|
||||||
|
std::string key, val;
|
||||||
|
};
|
||||||
|
|
||||||
|
class Map {
|
||||||
|
public:
|
||||||
|
std::vector<GenericClass*> mapdata;
|
||||||
|
Map();
|
||||||
|
Map(std::string path);
|
||||||
|
void Load(std::string path);
|
||||||
|
~Map();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
43
mpfw/MPFW_MPFWMF.h
Normal file
43
mpfw/MPFW_MPFWMF.h
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <raymath.h> // we shalln't worry ourselves with full raylib if we're going to end up networking this bad bitch
|
||||||
|
#include <raylib.h> // listening to le colibri nécrophile made me decide otherwise
|
||||||
|
#include <iostream>
|
||||||
|
#include <functional>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace MPFW {
|
||||||
|
namespace MPFW {
|
||||||
|
namespace MF {
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
UNKNOWN,
|
||||||
|
PLAYERSTART,
|
||||||
|
DECORATION
|
||||||
|
} EntityType;
|
||||||
|
|
||||||
|
class BaseEntity {
|
||||||
|
public:
|
||||||
|
EntityType entityType;
|
||||||
|
Vector3 origin;
|
||||||
|
virtual void Start(Map* map, int entityNum) = 0;
|
||||||
|
virtual void Update(Map* map, int entityNum) = 0;
|
||||||
|
virtual void Stop(Map* map, int entityNum) = 0;
|
||||||
|
virtual void Destroy(Map* map, int entityNum) = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
class Map {
|
||||||
|
public:
|
||||||
|
|
||||||
|
Model terrain;
|
||||||
|
|
||||||
|
|
||||||
|
Map();
|
||||||
|
|
||||||
|
|
||||||
|
~Map();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
461
mpfw/MPFW_Quake.cpp
Normal file
461
mpfw/MPFW_Quake.cpp
Normal file
@ -0,0 +1,461 @@
|
|||||||
|
|
||||||
|
#include <raylib.h>
|
||||||
|
#include "MPFW_Quake.h"
|
||||||
|
#include "MPFW_Utils.h"
|
||||||
|
#include <sstream>
|
||||||
|
#include <fstream>
|
||||||
|
#include <string>
|
||||||
|
#include <iostream>
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
#include <rlgl.h>
|
||||||
|
|
||||||
|
|
||||||
|
void MPFW::Quake::Maps::MapFile::LoadBSPMap(std::string path)
|
||||||
|
{
|
||||||
|
std::ifstream fileHandle(path, std::ios::binary); // open the file as bin
|
||||||
|
|
||||||
|
std::string buffer;
|
||||||
|
ds = Debug::NONE;
|
||||||
|
if (fileHandle.is_open() && fileHandle.good()) {
|
||||||
|
std::stringstream sstr; // get out of working with streams
|
||||||
|
sstr << fileHandle.rdbuf(); // as fast as possible
|
||||||
|
buffer = sstr.str(); // 3 LINES [WR SPEEDRUN]
|
||||||
|
|
||||||
|
// operating on files as a string or a char array is simpler
|
||||||
|
// than writing some fancy << fuckery (even though it is nice
|
||||||
|
// to see some << fuckery work without any moving parts)
|
||||||
|
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
fileHandle.close();
|
||||||
|
std::print("Bad file handle.\n");
|
||||||
|
ds = Debug::DONE;
|
||||||
|
throw std::runtime_error("Bad file handle in Quake BSP parsing.\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
fileHandle.close(); // close it as soon as we can get file loaded
|
||||||
|
// as to not disturb other programs
|
||||||
|
|
||||||
|
|
||||||
|
// we can now start the parsing
|
||||||
|
// hehehe >:3
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// if file is smaller than header
|
||||||
|
if (buffer.size() < 124) {
|
||||||
|
std::print("Bad file content size.\n");
|
||||||
|
throw std::runtime_error("File is smaller than header. Can't parse.");
|
||||||
|
}
|
||||||
|
|
||||||
|
ds = Debug::HEADER;
|
||||||
|
data.header.version = 0; // initialize version for addition
|
||||||
|
|
||||||
|
data.header.version += buffer[0];
|
||||||
|
data.header.version += buffer[1] << 8;
|
||||||
|
data.header.version += buffer[2] << 16;
|
||||||
|
data.header.version += buffer[3] << 24;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
std::vector<DirectoryEntry> vecdir;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
for (int i = 4; i < 123; i += 8) {
|
||||||
|
|
||||||
|
DirectoryEntry nd{0,0}; // new directory;
|
||||||
|
|
||||||
|
nd.offset = Utils::Strings::StringIndexToInteger_4b_le(buffer, i);
|
||||||
|
nd.size = Utils::Strings::StringIndexToInteger_4b_le(buffer, i + 4);
|
||||||
|
|
||||||
|
|
||||||
|
vecdir.push_back(nd);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// redistribution of entries
|
||||||
|
data.header.entities = vecdir[0];
|
||||||
|
data.header.planes = vecdir[1];
|
||||||
|
data.header.miptex = vecdir[2];
|
||||||
|
data.header.vertices = vecdir[3];
|
||||||
|
data.header.visilist = vecdir[4];
|
||||||
|
data.header.nodes = vecdir[5];
|
||||||
|
data.header.texinfo = vecdir[6];
|
||||||
|
data.header.faces = vecdir[7];
|
||||||
|
data.header.lightmaps = vecdir[8];
|
||||||
|
data.header.clipnodes = vecdir[9];
|
||||||
|
data.header.leaves = vecdir[10];
|
||||||
|
data.header.lface = vecdir[11];
|
||||||
|
data.header.edges = vecdir[12];
|
||||||
|
data.header.ledges = vecdir[13];
|
||||||
|
data.header.models = vecdir[14];
|
||||||
|
|
||||||
|
|
||||||
|
data.models.clear();
|
||||||
|
data.edges.clear();
|
||||||
|
data.faces.clear();
|
||||||
|
data.ledges.clear();
|
||||||
|
data.texInfo.clear();
|
||||||
|
data.vertices.clear();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Loading Models
|
||||||
|
// we're loading models first because that's
|
||||||
|
// what the spec does <3
|
||||||
|
|
||||||
|
ds = Debug::MODELS;
|
||||||
|
|
||||||
|
#ifdef MPFW_QUAKE_SLOW_LOADING
|
||||||
|
data.models.resize(data.header.models.size / 64);
|
||||||
|
|
||||||
|
modelsCDBG = 0;
|
||||||
|
for (int i = 0; i < data.header.models.size / 64; i++) {
|
||||||
|
int base = data.header.models.offset + 64 * i;
|
||||||
|
|
||||||
|
|
||||||
|
qModel qm;
|
||||||
|
|
||||||
|
|
||||||
|
qm.bound.min.x = Utils::Strings::StringIndexToFloat_4b_le(buffer, base);
|
||||||
|
qm.bound.min.y = Utils::Strings::StringIndexToFloat_4b_le(buffer, base + 4);
|
||||||
|
qm.bound.min.z = Utils::Strings::StringIndexToFloat_4b_le(buffer, base + 8);
|
||||||
|
qm.bound.max.x = Utils::Strings::StringIndexToFloat_4b_le(buffer, base + 12);
|
||||||
|
qm.bound.max.y = Utils::Strings::StringIndexToFloat_4b_le(buffer, base + 16);
|
||||||
|
qm.bound.max.z = Utils::Strings::StringIndexToFloat_4b_le(buffer, base + 20);
|
||||||
|
|
||||||
|
|
||||||
|
qm.origin.x = Utils::Strings::StringIndexToFloat_4b_le(buffer, base + 24);
|
||||||
|
qm.origin.y = Utils::Strings::StringIndexToFloat_4b_le(buffer, base + 28);
|
||||||
|
qm.origin.z = Utils::Strings::StringIndexToFloat_4b_le(buffer, base + 32);
|
||||||
|
|
||||||
|
qm.node_id0 = Utils::Strings::StringIndexToInteger_4b_le(buffer, base + 36);
|
||||||
|
qm.node_id1 = Utils::Strings::StringIndexToInteger_4b_le(buffer, base + 40);
|
||||||
|
qm.node_id2 = Utils::Strings::StringIndexToInteger_4b_le(buffer, base + 44);
|
||||||
|
qm.node_id3 = Utils::Strings::StringIndexToInteger_4b_le(buffer, base + 48);
|
||||||
|
|
||||||
|
|
||||||
|
qm.numleafs = Utils::Strings::StringIndexToInteger_4b_le(buffer, base + 52);
|
||||||
|
qm.face_id = Utils::Strings::StringIndexToInteger_4b_le(buffer, base + 56);
|
||||||
|
qm.face_num = Utils::Strings::StringIndexToInteger_4b_le(buffer, base + 60);
|
||||||
|
|
||||||
|
|
||||||
|
data.models[i] = qm;
|
||||||
|
modelsCDBG++;
|
||||||
|
|
||||||
|
|
||||||
|
// std::print("Model {}/{}\n", i, data.header.models.size / 64);
|
||||||
|
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
std::string modelsStr = Utils::Strings::IterativeStringExcerpt(buffer, data.header.models.offset, data.header.models.size);
|
||||||
|
data.models.resize(data.header.models.size / 64);
|
||||||
|
std::memcpy(data.models.data(), modelsStr.data(), data.header.models.size);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
// parsing vertices
|
||||||
|
|
||||||
|
ds = Debug::VERTICES;
|
||||||
|
#ifdef MPFW_QUAKE_SLOW_LOADING
|
||||||
|
verticesCDBG = 0;
|
||||||
|
data.vertices.resize(data.header.vertices.size / 12);
|
||||||
|
|
||||||
|
for (int i = 0; i < data.header.vertices.size / 12; i++) {
|
||||||
|
int base = data.header.vertices.offset + (12 * i);
|
||||||
|
|
||||||
|
Vector3 v;
|
||||||
|
|
||||||
|
v.x = Utils::Strings::StringIndexToFloat_4b_le(buffer, base);
|
||||||
|
v.y = Utils::Strings::StringIndexToFloat_4b_le(buffer, base + 4);
|
||||||
|
v.z = Utils::Strings::StringIndexToFloat_4b_le(buffer, base + 8);
|
||||||
|
|
||||||
|
data.vertices[i] = v;
|
||||||
|
|
||||||
|
// std::print("Vertex {}/{}\n", i, data.header.vertices.size / 12);
|
||||||
|
verticesCDBG++;
|
||||||
|
}
|
||||||
|
|
||||||
|
#else
|
||||||
|
|
||||||
|
std::string verticesStr = Utils::Strings::IterativeStringExcerpt(buffer, data.header.vertices.offset, data.header.vertices.size);
|
||||||
|
data.vertices.resize(data.header.vertices.size / 12);
|
||||||
|
std::memcpy(data.vertices.data(), verticesStr.data(), data.header.vertices.size);
|
||||||
|
#endif
|
||||||
|
// parsing edges
|
||||||
|
|
||||||
|
ds = Debug::EDGES;
|
||||||
|
|
||||||
|
#ifdef MPFW_QUAKE_SLOW_LOADING
|
||||||
|
edgesCDBG = 0;
|
||||||
|
data.edges.resize(data.header.edges.size / 4);
|
||||||
|
|
||||||
|
for (int i = 0; i < data.header.edges.size / 4; i++) {
|
||||||
|
int base = data.header.edges.offset + (4 * i);
|
||||||
|
Edge e;
|
||||||
|
e.vertex0 = Utils::Strings::StringIndexToInteger_2b_le(buffer, base);
|
||||||
|
e.vertex1 = Utils::Strings::StringIndexToInteger_2b_le(buffer, base+2);
|
||||||
|
data.edges[i] = e;
|
||||||
|
edgesCDBG++;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
|
||||||
|
std::string edgeStr = Utils::Strings::IterativeStringExcerpt(buffer, data.header.edges.offset, data.header.edges.size);
|
||||||
|
data.edges.resize(data.header.edges.size / 4);
|
||||||
|
std::memcpy(data.edges.data(), edgeStr.data(), data.header.edges.size);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
ds = Debug::TEXINFO;
|
||||||
|
|
||||||
|
#ifdef MPFW_QUAKE_SLOW_LOADING
|
||||||
|
texInfoCDBG = 0;
|
||||||
|
data.texInfo.resize(data.header.texinfo.size / 40);
|
||||||
|
for (int i = 0; i < data.header.texinfo.size / 40; i++) {
|
||||||
|
int base = data.header.texinfo.offset + (40 * i);
|
||||||
|
Surface s;
|
||||||
|
|
||||||
|
s.vectorS.x = Utils::Strings::StringIndexToFloat_4b_le(buffer, base);
|
||||||
|
s.vectorS.y = Utils::Strings::StringIndexToFloat_4b_le(buffer, base + 4);
|
||||||
|
s.vectorS.z = Utils::Strings::StringIndexToFloat_4b_le(buffer, base + 8);
|
||||||
|
s.distS = Utils::Strings::StringIndexToFloat_4b_le(buffer, base + 12);
|
||||||
|
s.vectorT.x = Utils::Strings::StringIndexToFloat_4b_le(buffer, base + 16);
|
||||||
|
s.vectorT.y = Utils::Strings::StringIndexToFloat_4b_le(buffer, base + 20);
|
||||||
|
s.vectorT.z = Utils::Strings::StringIndexToFloat_4b_le(buffer, base + 24);
|
||||||
|
s.distT = Utils::Strings::StringIndexToFloat_4b_le(buffer, base + 28);
|
||||||
|
s.textureId = Utils::Strings::StringIndexToInteger_4b_le(buffer, base + 32);
|
||||||
|
s.animated = Utils::Strings::StringIndexToInteger_4b_le(buffer, base + 36);
|
||||||
|
|
||||||
|
data.texInfo[i] = s;
|
||||||
|
texInfoCDBG++;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
|
||||||
|
std::string texInfoStr = Utils::Strings::IterativeStringExcerpt(buffer, data.header.texinfo.offset, data.header.texinfo.size);
|
||||||
|
data.texInfo.resize(data.header.texinfo.size / 40);
|
||||||
|
std::memcpy(data.texInfo.data(), texInfoStr.data(), data.header.texinfo.size);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
ds = Debug::LEDGES;
|
||||||
|
#ifdef MPFW_QUAKE_SLOW_LOADING
|
||||||
|
for (int i = 0; i < data.header.ledges.size / 2; i++) {
|
||||||
|
int base = data.header.ledges.offset + (2 * i);
|
||||||
|
|
||||||
|
|
||||||
|
data.ledges.push_back(Utils::Strings::StringIndexToInteger_2b_le(buffer, base));
|
||||||
|
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
|
||||||
|
|
||||||
|
std::string ledgesStr = Utils::Strings::IterativeStringExcerpt(buffer, data.header.ledges.offset, data.header.ledges.size);
|
||||||
|
data.ledges.resize(data.header.ledges.size / 4);
|
||||||
|
std::memcpy(data.ledges.data(), ledgesStr.data(), data.header.ledges.size);
|
||||||
|
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
// parsing faces
|
||||||
|
|
||||||
|
ds = Debug::FACES;
|
||||||
|
|
||||||
|
|
||||||
|
#ifdef MPFW_QUAKE_SLOW_LOADING
|
||||||
|
|
||||||
|
for (int i = 0; i < data.header.faces.size / 20; i++) {
|
||||||
|
int base = data.header.faces.offset + (20 * i);
|
||||||
|
|
||||||
|
Face f;
|
||||||
|
|
||||||
|
f.planeId = Utils::Strings::StringIndexToInteger_2b_le(buffer, base);
|
||||||
|
f.side = Utils::Strings::StringIndexToInteger_2b_le(buffer, base + 2);
|
||||||
|
|
||||||
|
f.ledgeId = (signed long)(Utils::Strings::StringIndexToInteger_4b_le(buffer, base + 4));
|
||||||
|
|
||||||
|
f.ledgeNum = Utils::Strings::StringIndexToInteger_2b_le(buffer, base + 8);
|
||||||
|
|
||||||
|
|
||||||
|
f.texinfoId = Utils::Strings::StringIndexToInteger_2b_le(buffer, base + 10);
|
||||||
|
|
||||||
|
f.typelight = buffer[base + 12];
|
||||||
|
f.baselight = buffer[base + 13];
|
||||||
|
f.light[0] = buffer[base + 14];
|
||||||
|
f.light[1] = buffer[base + 15];
|
||||||
|
f.lightmap = Utils::Strings::StringIndexToInteger_4b_le(buffer, base + 16);
|
||||||
|
|
||||||
|
|
||||||
|
data.faces.push_back(f);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#else
|
||||||
|
|
||||||
|
std::string facesStr = Utils::Strings::IterativeStringExcerpt(
|
||||||
|
buffer,
|
||||||
|
data.header.faces.offset,
|
||||||
|
data.header.faces.size
|
||||||
|
);
|
||||||
|
data.faces.resize(data.header.faces.size / 20);
|
||||||
|
std::memcpy(data.faces.data(), facesStr.data(), data.header.faces.size);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
std::print("Loading mip textures manually for now\n");
|
||||||
|
|
||||||
|
|
||||||
|
for (int i = 0; i < data.textures.size(); i++) {
|
||||||
|
rlUnloadTexture(data.textures[i].glTextureID);
|
||||||
|
}
|
||||||
|
cMipHeader cmh;
|
||||||
|
|
||||||
|
cmh.numtex = Utils::Strings::StringIndexToInteger_4b_le(buffer, data.header.miptex.offset);
|
||||||
|
std::vector<long> texOffsets;
|
||||||
|
// std::memcpy(texOffsets.data(), Utils::Strings::IterativeStringExcerpt(buffer, data.header.miptex.offset + 4, data.header.miptex.size - 4).data(), data.header.miptex.size - 4);
|
||||||
|
for (int i = 0; i < cmh.numtex; i++) {
|
||||||
|
texOffsets.push_back(Utils::Strings::StringIndexToInteger_4b_le(buffer, data.header.miptex.offset + 4 + 4*i));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
for (int i = 0; i < cmh.numtex; i++) {
|
||||||
|
unsigned int base = static_cast<unsigned int>(texOffsets[i]) + (unsigned)data.header.miptex.offset;
|
||||||
|
|
||||||
|
// this shit is ¤ undocumented! ¤
|
||||||
|
// fuck the unofficial quake specs
|
||||||
|
if (texOffsets[i] == -1) {
|
||||||
|
// is a texoffset of 0xFFFFFFFF a reference to the fact that
|
||||||
|
// it starts right after the mipheader? probably not, but it
|
||||||
|
// makes the texture loading code happy so... :shrug:
|
||||||
|
base = (unsigned)data.header.miptex.offset + 4 + texOffsets.size() * 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
cMiptex t;
|
||||||
|
std::memcpy(&t, Utils::Strings::IterativeStringExcerpt(buffer, base, 40).data(), 40);
|
||||||
|
|
||||||
|
Image img = GenImageColor(t.width, t.height, BLACK);
|
||||||
|
for (int y = 0; y < t.height; y++) {
|
||||||
|
for (int x = 0; x < t.width; x++) {
|
||||||
|
ImageDrawPixel(&img, x, y,
|
||||||
|
{
|
||||||
|
pal->data[(unsigned char)buffer[base + t.offset1 + (t.width * y + x)]].r,
|
||||||
|
pal->data[(unsigned char)buffer[base + t.offset1 + (t.width * y + x)]].g,
|
||||||
|
pal->data[(unsigned char)buffer[base + t.offset1 + (t.width * y + x)]].b,
|
||||||
|
255
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Texture2D temp = LoadTextureFromImage(img);
|
||||||
|
|
||||||
|
rlMipTex mt;
|
||||||
|
mt.glTextureID = temp.id;
|
||||||
|
mt.height = temp.height;
|
||||||
|
mt.width = temp.width;
|
||||||
|
mt.type = temp.format;
|
||||||
|
|
||||||
|
UnloadImage(img);
|
||||||
|
data.textures.push_back(mt);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
data.renderFaces.clear();
|
||||||
|
|
||||||
|
std::print("Precalculating faces and texture coordinates\n");
|
||||||
|
|
||||||
|
for (int i = 0; i < data.faces.size(); i++) {
|
||||||
|
CalculatedFace cface;
|
||||||
|
|
||||||
|
for (int k = 0; k < data.faces[i].ledgeNum; k++) {
|
||||||
|
if (data.ledges[(unsigned int)(data.faces[i].ledgeId) + k] < 0) {
|
||||||
|
cface.vertices.push_back(MPFW::Quake::Maps::qVec2RLVec(
|
||||||
|
data.vertices[
|
||||||
|
data.edges[
|
||||||
|
abs(
|
||||||
|
data.ledges[
|
||||||
|
data.faces[i].ledgeId + k
|
||||||
|
]
|
||||||
|
)
|
||||||
|
].vertex0
|
||||||
|
]
|
||||||
|
));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
cface.vertices.push_back(MPFW::Quake::Maps::qVec2RLVec(
|
||||||
|
data.vertices[
|
||||||
|
data.edges[
|
||||||
|
abs(
|
||||||
|
data.ledges[
|
||||||
|
data.faces[i].ledgeId + k
|
||||||
|
]
|
||||||
|
)
|
||||||
|
].vertex1
|
||||||
|
]
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
cface.glTextureId = data.textures[data.texInfo[data.faces[i].texinfoId].textureId].glTextureID;
|
||||||
|
cface.glTextureWidth = data.textures[data.texInfo[data.faces[i].texinfoId].textureId].width;
|
||||||
|
cface.glTextureHeight = data.textures[data.texInfo[data.faces[i].texinfoId].textureId].height;
|
||||||
|
|
||||||
|
|
||||||
|
for (int j = cface.vertices.size() - 1; j > 0; j--) {
|
||||||
|
|
||||||
|
|
||||||
|
cface.texCoords.push_back(Vector2(
|
||||||
|
(Vector3DotProduct(MPFW::Quake::Maps::RLVec2qVec(cface.vertices[0]), data.texInfo[data.faces[i].texinfoId].vectorS) + data.texInfo[data.faces[i].texinfoId].distS) / data.textures[data.texInfo[data.faces[i].texinfoId].textureId].width,
|
||||||
|
(Vector3DotProduct(MPFW::Quake::Maps::RLVec2qVec(cface.vertices[0]), data.texInfo[data.faces[i].texinfoId].vectorT) + data.texInfo[data.faces[i].texinfoId].distT) / data.textures[data.texInfo[data.faces[i].texinfoId].textureId].height
|
||||||
|
));
|
||||||
|
cface.texCoords.push_back(Vector2(
|
||||||
|
(Vector3DotProduct(MPFW::Quake::Maps::RLVec2qVec(cface.vertices[j]), data.texInfo[data.faces[i].texinfoId].vectorS) + data.texInfo[data.faces[i].texinfoId].distS) / data.textures[data.texInfo[data.faces[i].texinfoId].textureId].width,
|
||||||
|
(Vector3DotProduct(MPFW::Quake::Maps::RLVec2qVec(cface.vertices[j]), data.texInfo[data.faces[i].texinfoId].vectorT) + data.texInfo[data.faces[i].texinfoId].distT) / data.textures[data.texInfo[data.faces[i].texinfoId].textureId].height
|
||||||
|
));
|
||||||
|
cface.texCoords.push_back(Vector2(
|
||||||
|
(Vector3DotProduct(MPFW::Quake::Maps::RLVec2qVec(cface.vertices[j - 1]), data.texInfo[data.faces[i].texinfoId].vectorS) + data.texInfo[data.faces[i].texinfoId].distS) / data.textures[data.texInfo[data.faces[i].texinfoId].textureId].width,
|
||||||
|
(Vector3DotProduct(MPFW::Quake::Maps::RLVec2qVec(cface.vertices[j - 1]), data.texInfo[data.faces[i].texinfoId].vectorT) + data.texInfo[data.faces[i].texinfoId].distT) / data.textures[data.texInfo[data.faces[i].texinfoId].textureId].height
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
data.renderFaces.push_back(cface);
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
ds = Debug::DONE;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
Vector3 MPFW::Quake::Maps::qVec2RLVec(Vector3 q) {
|
||||||
|
return { q.y / 2, q.z / 2, q.x / 2 };
|
||||||
|
}
|
||||||
|
Vector3 MPFW::Quake::Maps::RLVec2qVec(Vector3 q) {
|
||||||
|
return { q.z * 2, q.x * 2, q.y * 2 };
|
||||||
|
}
|
||||||
|
|
||||||
|
void MPFW::Quake::Maps::Palette::LoadPalette(std::string path)
|
||||||
|
{
|
||||||
|
std::ifstream i(path, std::ios::binary);
|
||||||
|
if (i.is_open() && i.good()) {
|
||||||
|
data.clear();
|
||||||
|
data.resize(256);
|
||||||
|
std::stringstream s;
|
||||||
|
s << i.rdbuf();
|
||||||
|
std::string d = s.str();
|
||||||
|
|
||||||
|
std::memcpy(data.data(), d.data(), 256 * 3);
|
||||||
|
|
||||||
|
}
|
||||||
|
else throw std::runtime_error("can't open palette file " + path);
|
||||||
|
}
|
235
mpfw/MPFW_Quake.h
Normal file
235
mpfw/MPFW_Quake.h
Normal file
@ -0,0 +1,235 @@
|
|||||||
|
#pragma once
|
||||||
|
// based off of https://www.gamers.org/dEngine/quake/spec/quake-spec34/qkspec_4.htm
|
||||||
|
// (mostly)
|
||||||
|
|
||||||
|
#include <raymath.h>
|
||||||
|
#include <iostream>
|
||||||
|
#include <vector>
|
||||||
|
#include <print>
|
||||||
|
|
||||||
|
namespace MPFW {
|
||||||
|
namespace Quake {
|
||||||
|
namespace Maps{
|
||||||
|
|
||||||
|
|
||||||
|
namespace Debug {
|
||||||
|
enum DebugState {
|
||||||
|
NONE,
|
||||||
|
HEADER,
|
||||||
|
MODELS,
|
||||||
|
VERTICES,
|
||||||
|
EDGES,
|
||||||
|
TEXINFO,
|
||||||
|
LEDGES,
|
||||||
|
FACES,
|
||||||
|
DONE
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Vector3 qVec2RLVec(Vector3 q);
|
||||||
|
Vector3 RLVec2qVec(Vector3 q);
|
||||||
|
|
||||||
|
struct DirectoryEntry {
|
||||||
|
long offset;
|
||||||
|
long size;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
struct BSPHeader {
|
||||||
|
long version; // must be 0x17
|
||||||
|
DirectoryEntry entities; // list of entities
|
||||||
|
DirectoryEntry planes; // planes
|
||||||
|
DirectoryEntry miptex; // wall textures
|
||||||
|
DirectoryEntry vertices; // map vertices
|
||||||
|
DirectoryEntry visilist; // leaves visibility lists
|
||||||
|
DirectoryEntry nodes; // bsp nodes
|
||||||
|
DirectoryEntry texinfo; // texture info for faces
|
||||||
|
DirectoryEntry faces; // faces of each surface
|
||||||
|
DirectoryEntry lightmaps; // wall light maps
|
||||||
|
DirectoryEntry clipnodes; // clip nodes
|
||||||
|
DirectoryEntry leaves; // bsp leaves
|
||||||
|
DirectoryEntry lface; // list of faces
|
||||||
|
DirectoryEntry edges; // edges of faces
|
||||||
|
DirectoryEntry ledges; // list of edges
|
||||||
|
DirectoryEntry models; // list of models
|
||||||
|
};
|
||||||
|
|
||||||
|
struct qBoundingBox {
|
||||||
|
Vector3 min,max;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct qBoundingBoxShort {
|
||||||
|
short minX, minY, minZ, maxX, maxY, maxZ;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct qModel {
|
||||||
|
qBoundingBox bound;
|
||||||
|
Vector3 origin;
|
||||||
|
long node_id0, // first bsp node
|
||||||
|
node_id1, // first clip node
|
||||||
|
node_id2, // second clip node
|
||||||
|
node_id3; // usually zero
|
||||||
|
long numleafs;
|
||||||
|
long face_id,
|
||||||
|
face_num;
|
||||||
|
};
|
||||||
|
struct Edge {
|
||||||
|
unsigned short vertex0, vertex1;
|
||||||
|
};
|
||||||
|
struct Surface {
|
||||||
|
Vector3 vectorS;
|
||||||
|
float distS;
|
||||||
|
Vector3 vectorT;
|
||||||
|
float distT;
|
||||||
|
unsigned long textureId;
|
||||||
|
unsigned long animated;
|
||||||
|
};
|
||||||
|
struct Face {
|
||||||
|
unsigned short planeId;
|
||||||
|
unsigned short side;
|
||||||
|
long ledgeId;
|
||||||
|
unsigned short ledgeNum;
|
||||||
|
unsigned short texinfoId;
|
||||||
|
|
||||||
|
unsigned char typelight, baselight;
|
||||||
|
unsigned char light[2];
|
||||||
|
long lightmap;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
struct CalculatedFace {
|
||||||
|
std::vector<Vector3> vertices;
|
||||||
|
std::vector<Vector2> texCoords;
|
||||||
|
int glTextureId, glTextureWidth, glTextureHeight;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
struct cMipHeader {
|
||||||
|
long numtex;
|
||||||
|
void *offset;
|
||||||
|
};
|
||||||
|
|
||||||
|
using MipHeader = std::vector<long>;
|
||||||
|
|
||||||
|
struct cMiptex{
|
||||||
|
char name[16];
|
||||||
|
unsigned long width, height,
|
||||||
|
offset1, offset2, offset4, offset8;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct rlMipTex {
|
||||||
|
char name[16];
|
||||||
|
unsigned long width, height;
|
||||||
|
unsigned long glTextureID;
|
||||||
|
int type;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
struct Node {
|
||||||
|
long planeId;
|
||||||
|
unsigned short front, back;
|
||||||
|
qBoundingBoxShort box;
|
||||||
|
unsigned short faceId, faceNum;
|
||||||
|
};
|
||||||
|
struct Leaf {
|
||||||
|
long type, vislist;
|
||||||
|
qBoundingBoxShort bound;
|
||||||
|
unsigned short lfaceId, lfaceNum;
|
||||||
|
unsigned char sndWater, sndSky, sndSlime, sndLava;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
struct qColor {
|
||||||
|
unsigned char r, g, b;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
class Palette {
|
||||||
|
public:
|
||||||
|
std::vector<qColor> data;
|
||||||
|
Palette() { data = std::vector<qColor>(256); }
|
||||||
|
Palette(std::string path) { LoadPalette(path); }
|
||||||
|
|
||||||
|
void LoadPalette(std::string path);
|
||||||
|
~Palette() {}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct MapData {
|
||||||
|
BSPHeader header;
|
||||||
|
|
||||||
|
|
||||||
|
std::vector<Vector3> vertices;
|
||||||
|
std::vector<Edge> edges;
|
||||||
|
std::vector<qModel> models;
|
||||||
|
|
||||||
|
std::vector<Surface> texInfo;
|
||||||
|
std::vector<Face> faces;
|
||||||
|
std::vector<int> ledges;
|
||||||
|
std::vector<rlMipTex> textures;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
std::vector<CalculatedFace> renderFaces;
|
||||||
|
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
class MapFile {
|
||||||
|
public:
|
||||||
|
Debug::DebugState ds = Debug::NONE;
|
||||||
|
MapData data;
|
||||||
|
Palette* pal;
|
||||||
|
|
||||||
|
int modelsCDBG = 0;
|
||||||
|
int verticesCDBG = 0;
|
||||||
|
int edgesCDBG = 0;
|
||||||
|
int texInfoCDBG = 0;
|
||||||
|
int facesCDBG = 0;
|
||||||
|
|
||||||
|
MapFile(){}
|
||||||
|
MapFile(std::string path) {
|
||||||
|
LoadBSPMap(path);
|
||||||
|
}
|
||||||
|
void LoadBSPMap(std::string path);
|
||||||
|
~MapFile(){}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
template<>
|
||||||
|
struct std::formatter<MPFW::Quake::Maps::DirectoryEntry> {
|
||||||
|
|
||||||
|
|
||||||
|
constexpr auto parse(std::format_parse_context& ctx) { return ctx.begin(); }
|
||||||
|
auto format(const MPFW::Quake::Maps::DirectoryEntry& in, std::format_context& ctx) const {
|
||||||
|
return std::format_to(ctx.out(), "([MPFW::Quake::Maps::DirectoryEntry]: at pos {}, {}B in size)", in.offset, in.size);
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
template<>
|
||||||
|
struct std::formatter<MPFW::Quake::Maps::MapData> {
|
||||||
|
|
||||||
|
|
||||||
|
constexpr auto parse(std::format_parse_context& ctx) { return ctx.begin(); }
|
||||||
|
auto format(const MPFW::Quake::Maps::MapData& in, std::format_context& ctx) const {
|
||||||
|
return std::format_to(ctx.out(),
|
||||||
|
"([MPFW::Quake::Maps::MapFile]:\n"
|
||||||
|
" Vertices: {}\n"
|
||||||
|
" Edges: {}\n"
|
||||||
|
" Models: {}\n"
|
||||||
|
" TexInfo: {}\n"
|
||||||
|
" Faces: {}\n"
|
||||||
|
" Ledges: {}\n"
|
||||||
|
")", in.vertices.size(), in.edges.size(), in.models.size(), in.texInfo.size(), in.faces.size(), in.ledges.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
5
mpfw/MPFW_UI.h
Normal file
5
mpfw/MPFW_UI.h
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
namespace MPFW {
|
||||||
|
|
||||||
|
}
|
153
mpfw/MPFW_Utils.cpp
Normal file
153
mpfw/MPFW_Utils.cpp
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
#include "MPFW_Utils.h"
|
||||||
|
#include <Windows.h>
|
||||||
|
#include <gl/GL.h>
|
||||||
|
#include <gl/GLU.h>
|
||||||
|
bool MPFW::Utils::Strings::IterativeComp(std::string originalString, int startPointer, std::string toCompare)
|
||||||
|
{
|
||||||
|
bool retVal = true;
|
||||||
|
for (int i = 0; i < toCompare.length(); i++) {
|
||||||
|
retVal = originalString.at(startPointer + i) == toCompare.at(i);
|
||||||
|
if (retVal == false) break;
|
||||||
|
}
|
||||||
|
return retVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string MPFW::Utils::Strings::IterativeStringExcerpt(std::string originalString, int startPointer, int length)
|
||||||
|
{
|
||||||
|
std::string retval = "";
|
||||||
|
if (startPointer == -1) return retval;
|
||||||
|
for (int i = 0; i < length; i++) {
|
||||||
|
retval += originalString[startPointer + i];
|
||||||
|
}
|
||||||
|
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string MPFW::Utils::Strings::IterativeStringExcerpt_delim(std::string originalString, int startPointer, char delimiter)
|
||||||
|
{
|
||||||
|
std::string retval = "";
|
||||||
|
if (startPointer == -1) return retval;
|
||||||
|
for (int i = startPointer; i < originalString.size(); i++) {
|
||||||
|
if (originalString[i] == delimiter) break;
|
||||||
|
retval += originalString[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t MPFW::Utils::Strings::StringIndexToInteger_4b_le(std::string originalString, int startPointer)
|
||||||
|
{
|
||||||
|
int32_t retval;
|
||||||
|
|
||||||
|
retval = (unsigned char)originalString.at(startPointer + 3) << 24;
|
||||||
|
retval += (unsigned char)originalString.at(startPointer + 2) << 16;
|
||||||
|
retval += (unsigned char)originalString.at(startPointer + 1) << 8;
|
||||||
|
retval += (unsigned char)originalString.at(startPointer);
|
||||||
|
|
||||||
|
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t MPFW::Utils::Strings::StringIndexToInteger_2b_le(std::string originalString, int startPointer)
|
||||||
|
{
|
||||||
|
int32_t retval;
|
||||||
|
|
||||||
|
retval = (unsigned char)originalString.at(startPointer + 1) << 8;
|
||||||
|
retval += (unsigned char)originalString.at(startPointer);
|
||||||
|
|
||||||
|
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::tuple<std::string, std::string> MPFW::Utils::Strings::ParseCLineIntoKeyVal(std::string originalString)
|
||||||
|
{
|
||||||
|
std::tuple<std::string, std::string> retval;
|
||||||
|
|
||||||
|
int equPos = -1, valstartpos = -1, valendpos = -1;
|
||||||
|
bool inQuotes = false;
|
||||||
|
|
||||||
|
for (int i = 0; i < originalString.size(); i++) {
|
||||||
|
if (originalString[i] == '=' and equPos == -1) {
|
||||||
|
equPos = i;
|
||||||
|
valstartpos = i + 1;
|
||||||
|
}
|
||||||
|
if (originalString[i] == '"') {
|
||||||
|
if (inQuotes) {
|
||||||
|
valendpos = i - 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (valstartpos == -1) {
|
||||||
|
valstartpos = i + 1;
|
||||||
|
inQuotes = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (originalString[i] == ';' and not inQuotes) {
|
||||||
|
valendpos = i - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
retval = std::make_tuple<std::string, std::string>(IterativeStringExcerpt(originalString, 0, equPos), IterativeStringExcerpt(originalString, valstartpos, valendpos - valstartpos + 1));
|
||||||
|
}
|
||||||
|
catch(...){
|
||||||
|
retval = std::make_tuple<std::string, std::string>("", "");
|
||||||
|
}
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string MPFW::Utils::Strings::SingleWordTrimmer(std::string originalString)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < originalString.size(); i++) {
|
||||||
|
if (originalString[i] == ' ' or originalString[i] == '/') return IterativeStringExcerpt(originalString, 0, i);
|
||||||
|
}
|
||||||
|
|
||||||
|
return originalString;
|
||||||
|
}
|
||||||
|
|
||||||
|
float MPFW::Utils::Strings::StringIndexToFloat_4b_le(std::string originalString, int startPointer)
|
||||||
|
{
|
||||||
|
|
||||||
|
|
||||||
|
unsigned char buf[4];
|
||||||
|
|
||||||
|
buf[0] = originalString[startPointer];
|
||||||
|
buf[1] = originalString[startPointer + 1];
|
||||||
|
buf[2] = originalString[startPointer + 2];
|
||||||
|
buf[3] = originalString[startPointer + 3];
|
||||||
|
|
||||||
|
|
||||||
|
float retval = (*(float*)buf);
|
||||||
|
|
||||||
|
return retval;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
std::basic_string<MPFW::Utils::Geometry::Triangle2D> MPFW::Utils::Geometry::TesselateVertexString(VertexString vs)
|
||||||
|
{
|
||||||
|
|
||||||
|
std::basic_string<Triangle2D> retval;
|
||||||
|
|
||||||
|
/*GLUtesselator* tess = gluNewTess();
|
||||||
|
|
||||||
|
|
||||||
|
gluTessBeginPolygon(tess, 0);
|
||||||
|
|
||||||
|
for (int i = 0; i < vs.size(); i++) {
|
||||||
|
GLdouble* coords;
|
||||||
|
coords[0] = static_cast<double>(vs[i].x);
|
||||||
|
coords[1] = 0;
|
||||||
|
coords[2] = static_cast<double>(vs[i].y);
|
||||||
|
gluTessVertex(tess, coords, coords);
|
||||||
|
}
|
||||||
|
gluTessEndPolygon(tess);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
gluDeleteTess(tess);
|
||||||
|
|
||||||
|
*/
|
||||||
|
throw;
|
||||||
|
|
||||||
|
return retval;
|
||||||
|
}
|
28
mpfw/MPFW_Utils.h
Normal file
28
mpfw/MPFW_Utils.h
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
namespace MPFW {
|
||||||
|
namespace Utils {
|
||||||
|
namespace Geometry {
|
||||||
|
struct Vertex2D {
|
||||||
|
float x, y;
|
||||||
|
};
|
||||||
|
struct Triangle2D {
|
||||||
|
float a, b, c;
|
||||||
|
};
|
||||||
|
using VertexString = std::basic_string<Vertex2D>;
|
||||||
|
|
||||||
|
std::basic_string<Triangle2D> TesselateVertexString(VertexString vs);
|
||||||
|
}
|
||||||
|
namespace Strings {
|
||||||
|
bool IterativeComp(std::string originalString, int startPointer, std::string toCompare);
|
||||||
|
std::string IterativeStringExcerpt(std::string originalString, int startPointer, int length);
|
||||||
|
std::string IterativeStringExcerpt_delim(std::string originalString, int startPointer, char delimiter);
|
||||||
|
int32_t StringIndexToInteger_4b_le(std::string originalString, int startPointer);
|
||||||
|
int32_t StringIndexToInteger_2b_le(std::string originalString, int startPointer);
|
||||||
|
std::tuple<std::string, std::string> ParseCLineIntoKeyVal(std::string originalString);
|
||||||
|
std::string SingleWordTrimmer(std::string originalString);
|
||||||
|
float StringIndexToFloat_4b_le(std::string originalString, int startPointer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
530
mpfw/main.cpp
Normal file
530
mpfw/main.cpp
Normal file
@ -0,0 +1,530 @@
|
|||||||
|
#include <iostream>
|
||||||
|
#include <raylib.h>
|
||||||
|
|
||||||
|
#include <raymath.h>
|
||||||
|
#include <fstream>
|
||||||
|
#include <sstream>
|
||||||
|
#include <string>
|
||||||
|
#include "MPFW_Quake.h"
|
||||||
|
#include "MPFW_Console.h"
|
||||||
|
#include <print>
|
||||||
|
#include <rlgl.h>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
|
|
||||||
|
// turn quake miptex into rl texture
|
||||||
|
Texture2D RLMT_QUAKE(MPFW::Quake::Maps::rlMipTex rlmt) {
|
||||||
|
Texture2D retval;
|
||||||
|
|
||||||
|
|
||||||
|
retval.id = rlmt.glTextureID;
|
||||||
|
retval.width = rlmt.width;
|
||||||
|
retval.height = rlmt.height;
|
||||||
|
retval.format = rlmt.type;
|
||||||
|
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Camera camera = { 0 };
|
||||||
|
|
||||||
|
Vector3 rotation = { 1,0,0 };
|
||||||
|
Vector3 hRotation = { 1,0,0 };
|
||||||
|
Vector3 velocity = { 0,0,0 };
|
||||||
|
float accelFactor = 300.0f;
|
||||||
|
|
||||||
|
struct ConsoleSettings {
|
||||||
|
int height;
|
||||||
|
};
|
||||||
|
|
||||||
|
// this is grossly imprecise because of a lack of direct interfaces
|
||||||
|
// with the loop, but it's simple and should be performant in a
|
||||||
|
// thread
|
||||||
|
//static void __DebugCounter_Quake(MPFW::Quake::Maps::MapFile* mapFile) {
|
||||||
|
// while (mapFile->ds != MPFW::Quake::Maps::Debug::DONE) {
|
||||||
|
// switch (mapFile->ds) {
|
||||||
|
// case MPFW::Quake::Maps::Debug::HEADER:
|
||||||
|
// std::print("Reading header\n");
|
||||||
|
// break;
|
||||||
|
// case MPFW::Quake::Maps::Debug::MODELS:
|
||||||
|
// std::print("Model {}/{}\n", mapFile->modelsCDBG, mapFile->data.header.models.size / 64);
|
||||||
|
// break;
|
||||||
|
// case MPFW::Quake::Maps::Debug::VERTICES:
|
||||||
|
// std::print("Vertex {}/{}\n", mapFile->verticesCDBG, mapFile->data.header.vertices.size / 12);
|
||||||
|
// break;
|
||||||
|
// case MPFW::Quake::Maps::Debug::EDGES:
|
||||||
|
// std::print("Edge {}/{}\n", mapFile->edgesCDBG, mapFile->data.header.edges.size / 4);
|
||||||
|
// break;
|
||||||
|
// case MPFW::Quake::Maps::Debug::TEXINFO:
|
||||||
|
// std::print("Surface {}/{}\n", mapFile->texInfoCDBG, mapFile->data.header.texinfo.size / 40);
|
||||||
|
// break;
|
||||||
|
// case MPFW::Quake::Maps::Debug::LEDGES:
|
||||||
|
// std::print("Ledge {}/{}\n", mapFile->data.ledges.size(), mapFile->data.header.ledges.size / 2);
|
||||||
|
// break;
|
||||||
|
// case MPFW::Quake::Maps::Debug::FACES:
|
||||||
|
// std::print("Face {}/{}\n", mapFile->data.faces.size(), mapFile->data.header.faces.size / 20);
|
||||||
|
// break;
|
||||||
|
// }
|
||||||
|
//#pragma warning(suppress : 4996)
|
||||||
|
// _sleep(250);
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void Look() {
|
||||||
|
|
||||||
|
hRotation = Vector3RotateByAxisAngle(hRotation, { 0,1,0 }, -GetMouseDelta().x * GetFrameTime());
|
||||||
|
rotation = Vector3RotateByAxisAngle(rotation, { 0,1,0 }, -GetMouseDelta().x * GetFrameTime());
|
||||||
|
rotation = Vector3Normalize({ rotation.x, Clamp(rotation.y - GetMouseDelta().y * GetFrameTime(), -0.99, 0.99), rotation.z });
|
||||||
|
}
|
||||||
|
|
||||||
|
void AirAccelerate(bool inAir) {
|
||||||
|
if (IsKeyDown(KEY_W)) {
|
||||||
|
velocity += hRotation * GetFrameTime() * accelFactor * (IsKeyDown(KEY_LEFT_SHIFT) ? 2 : 1);
|
||||||
|
}
|
||||||
|
if (IsKeyDown(KEY_S)) {
|
||||||
|
velocity -= hRotation * GetFrameTime() * accelFactor;
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector3 sideRotation = Vector3RotateByAxisAngle(hRotation, { 0,1,0 }, DEG2RAD * 90);
|
||||||
|
|
||||||
|
if (IsKeyDown(KEY_A)) {
|
||||||
|
velocity += sideRotation * GetFrameTime() * accelFactor * (IsKeyDown(KEY_LEFT_SHIFT) ? 2 : 1);
|
||||||
|
}
|
||||||
|
if (IsKeyDown(KEY_D)) {
|
||||||
|
velocity -= sideRotation * GetFrameTime() * accelFactor * (IsKeyDown(KEY_LEFT_SHIFT) ? 2 : 1);
|
||||||
|
}
|
||||||
|
if (inAir)velocity.y -= GetFrameTime();
|
||||||
|
velocity.x *= 16 * GetFrameTime();
|
||||||
|
velocity.z *= 16 * GetFrameTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Accelerate() {
|
||||||
|
if (IsKeyDown(KEY_SPACE))velocity.y += accelFactor*0.01;
|
||||||
|
AirAccelerate(false);
|
||||||
|
AirAccelerate(false);
|
||||||
|
|
||||||
|
if (velocity.y < 0) velocity.y = 0;
|
||||||
|
velocity *= 16*GetFrameTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#ifdef _DEBUG
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
void ___test___(T a, T b) {
|
||||||
|
if (a != b) throw;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
|
||||||
|
|
||||||
|
#ifdef _DEBUG
|
||||||
|
|
||||||
|
std::print("COMPILED IN DEBUG MODE\n----------------------\n\n");
|
||||||
|
|
||||||
|
std::print("Unit testing\n\n\n");
|
||||||
|
|
||||||
|
{ // UT01 : formatting a quake map directory entry
|
||||||
|
std::print("UT01 : formatting a quake map directory entry\n");
|
||||||
|
MPFW::Quake::Maps::DirectoryEntry dirent;
|
||||||
|
|
||||||
|
dirent.offset = 1000;
|
||||||
|
dirent.size = 2000;
|
||||||
|
|
||||||
|
std::string retval = std::format("{}", dirent);
|
||||||
|
std::print("{}", retval);
|
||||||
|
___test___(retval, std::string("([MPFW::Quake::Maps::DirectoryEntry]: at pos 1000, 2000B in size)"));
|
||||||
|
|
||||||
|
std::println();
|
||||||
|
std::print("UT01 Success\n");
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
std::print("\nEnd of unit testing\n\n\n");
|
||||||
|
#endif
|
||||||
|
|
||||||
|
MPFW::Quake::Maps::MapFile map;
|
||||||
|
// std::thread t(__DebugCounter_Quake, &map);
|
||||||
|
|
||||||
|
// t.detach();
|
||||||
|
|
||||||
|
std::vector<Color> colors;
|
||||||
|
|
||||||
|
for (int i = 0; i < 3000000; i++) {
|
||||||
|
colors.push_back({ (unsigned char)GetRandomValue(0, 255), (unsigned char)GetRandomValue(0, 255), (unsigned char)GetRandomValue(0, 255), 255 });
|
||||||
|
}
|
||||||
|
|
||||||
|
std::print("MAP DATA:\n---------\n\n");
|
||||||
|
std::print("Version: {}\n", map.data.header.version);
|
||||||
|
std::print("\n---\nDIRENTS\n-------\n");
|
||||||
|
std::print("Entities: {}\n", map.data.header.entities);
|
||||||
|
std::print("Planes: {}\n", map.data.header.planes);
|
||||||
|
std::print("Wall Textures (miptex): {}\n", map.data.header.miptex);
|
||||||
|
std::print("Map Vertices: {}\n", map.data.header.vertices);
|
||||||
|
std::print("Leaves Visibility Lists: {}\n", map.data.header.visilist);
|
||||||
|
std::print("BSP Nodes: {}\n", map.data.header.nodes);
|
||||||
|
std::print("Texture Info for Faces: {}\n", map.data.header.texinfo);
|
||||||
|
std::print("Faces of each surface: {}\n", map.data.header.faces);
|
||||||
|
std::print("Wall Lightmaps: {}\n", map.data.header.lightmaps);
|
||||||
|
std::print("Clip Nodes: {}\n", map.data.header.clipnodes);
|
||||||
|
std::print("BSP Leaves: {}\n", map.data.header.leaves);
|
||||||
|
std::print("List of Faces: {}\n", map.data.header.lface);
|
||||||
|
std::print("Edges of Faces: {}\n", map.data.header.edges);
|
||||||
|
std::print("List of Edges: {}\n", map.data.header.ledges);
|
||||||
|
std::print("Models: {}\n", map.data.header.models);
|
||||||
|
|
||||||
|
std::print("---\n\n");
|
||||||
|
|
||||||
|
std::print("Vertex count: {} (on {} in header)\n", map.data.vertices.size(), map.data.header.vertices.size / sizeof(Vector3));
|
||||||
|
std::print("Edge count: {} (on {} in header)\n", map.data.edges.size(), map.data.header.edges.size / 4);
|
||||||
|
|
||||||
|
std::print("Model count: {} (on {} in header)\n", map.data.models.size(), map.data.header.models.size / sizeof(MPFW::Quake::Maps::qModel));
|
||||||
|
for (int i = 0; i < map.data.models.size(); i++) {
|
||||||
|
if (map.data.models[i].node_id3 != 0) {
|
||||||
|
std::print("node 3 on {} isn't 0 ({})\n", i, map.data.models[i].node_id3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
map.pal = new MPFW::Quake::Maps::Palette("data/palette.lmp");
|
||||||
|
|
||||||
|
SetConfigFlags(FLAG_WINDOW_RESIZABLE);
|
||||||
|
|
||||||
|
InitWindow(1280, 720, TextFormat("mpfw"));
|
||||||
|
SetTargetFPS(60);
|
||||||
|
SetExitKey(0);
|
||||||
|
DisableCursor();
|
||||||
|
|
||||||
|
|
||||||
|
MPFW::Console::CommandHandlerResources chr;
|
||||||
|
chr.mapQuake = ↦
|
||||||
|
|
||||||
|
MPFW::Console::CommandHandler cmdH(&chr);
|
||||||
|
|
||||||
|
Font robotoMonoRegular = LoadFontEx("data/fonts/RobotoMono/RobotoMono-Regular.ttf", 30, NULL, 6000);
|
||||||
|
int robotoMonoHeight = MeasureTextEx(robotoMonoRegular, "X", 30, 0).y;
|
||||||
|
|
||||||
|
|
||||||
|
cmdH.RunScript("data/cfg/startup.cfg");
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// rlSetClipPlanes(1, INFINITY);
|
||||||
|
camera.position = { 0,5,0 };
|
||||||
|
camera.target = { 1,0,0 };
|
||||||
|
camera.fovy = 120;
|
||||||
|
camera.up = { 0,1,0 };
|
||||||
|
camera.projection = CAMERA_PERSPECTIVE;
|
||||||
|
|
||||||
|
bool indivFaceMode = false;
|
||||||
|
int indivFace = 0;
|
||||||
|
bool consoleOn = false;
|
||||||
|
std::string cmdBuf = "";
|
||||||
|
while (!WindowShouldClose()) {
|
||||||
|
if (IsKeyPressed(KEY_APOSTROPHE)) {
|
||||||
|
consoleOn = !consoleOn;
|
||||||
|
}
|
||||||
|
if(!consoleOn){
|
||||||
|
Look();
|
||||||
|
if (camera.position.y > 6) AirAccelerate(true);
|
||||||
|
else Accelerate();
|
||||||
|
|
||||||
|
|
||||||
|
if (IsKeyPressed(KEY_LEFT) && indivFace > 0) indivFace--;
|
||||||
|
if (IsKeyPressed(KEY_RIGHT) && indivFace < map.data.faces.size() - 1) indivFace++;
|
||||||
|
if (IsKeyPressed(KEY_F)) indivFaceMode = !indivFaceMode;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
camera.position += velocity;
|
||||||
|
camera.target = camera.position + rotation;
|
||||||
|
BeginDrawing();
|
||||||
|
ClearBackground(BLACK);
|
||||||
|
DrawRectangleGradientV(0, 0, GetScreenWidth(), GetScreenHeight(), WHITE, LIGHTGRAY);
|
||||||
|
|
||||||
|
BeginMode3D(camera);
|
||||||
|
// DrawGrid(1000, 10);
|
||||||
|
if (!indivFaceMode) {
|
||||||
|
for (int i = 0; i < map.data.vertices.size(); i++) {
|
||||||
|
// DrawPoint3D(MPFW::Quake::Maps::qVec2RLVec(map.data.vertices[i]), BLACK);
|
||||||
|
// if (i != 0) DrawLine3D(MPFW::Quake::Maps::qVec2RLVec(map.data.vertices[i]), MPFW::Quake::Maps::qVec2RLVec(map.data.vertices[i - 1]), PURPLE);
|
||||||
|
}
|
||||||
|
for (int i = 0; i < map.data.edges.size(); i++) {
|
||||||
|
// DrawLine3D(MPFW::Quake::Maps::qVec2RLVec(map.data.vertices[map.data.edges[i].vertex0]), MPFW::Quake::Maps::qVec2RLVec(map.data.vertices[map.data.edges[i].vertex1]), PURPLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < map.data.faces.size(); i++) {
|
||||||
|
|
||||||
|
std::vector<Vector3> vertices;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Vector3 a = MPFW::Quake::Maps::qVec2RLVec(map.data.vertices[map.data.edges[abs(map.data.ledges[map.data.faces[i].ledgeId])].vertex0]);
|
||||||
|
|
||||||
|
|
||||||
|
for (int k = 0; k < map.data.faces[i].ledgeNum; k++) {
|
||||||
|
if (map.data.ledges[(unsigned int)(map.data.faces[i].ledgeId) + k] < 0) {
|
||||||
|
vertices.push_back(MPFW::Quake::Maps::qVec2RLVec(
|
||||||
|
map.data.vertices[
|
||||||
|
map.data.edges[
|
||||||
|
abs(
|
||||||
|
map.data.ledges[
|
||||||
|
map.data.faces[i].ledgeId + k
|
||||||
|
]
|
||||||
|
)
|
||||||
|
].vertex0
|
||||||
|
]
|
||||||
|
));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
vertices.push_back(MPFW::Quake::Maps::qVec2RLVec(
|
||||||
|
map.data.vertices[
|
||||||
|
map.data.edges[
|
||||||
|
abs(
|
||||||
|
map.data.ledges[
|
||||||
|
map.data.faces[i].ledgeId + k
|
||||||
|
]
|
||||||
|
)
|
||||||
|
].vertex1
|
||||||
|
]
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*DrawLine3D(MPFW::Quake::Maps::qVec2RLVec(
|
||||||
|
map.data.vertices[
|
||||||
|
map.data.edges[
|
||||||
|
abs(
|
||||||
|
map.data.ledges[
|
||||||
|
map.data.faces[i].ledgeId + k
|
||||||
|
]
|
||||||
|
)
|
||||||
|
].vertex0
|
||||||
|
]
|
||||||
|
),
|
||||||
|
|
||||||
|
MPFW::Quake::Maps::qVec2RLVec(
|
||||||
|
map.data.vertices[
|
||||||
|
map.data.edges[
|
||||||
|
abs(
|
||||||
|
map.data.ledges[
|
||||||
|
map.data.faces[i].ledgeId + k
|
||||||
|
]
|
||||||
|
)
|
||||||
|
].vertex1
|
||||||
|
]
|
||||||
|
), RED);*/
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
for(int j = vertices.size() - 1; j > 0; j--){
|
||||||
|
// rlColor4ub(colors[i].r, colors[i].g, colors[i].b, colors[i].a);
|
||||||
|
rlColor4ub(255, 255, 255, 255);
|
||||||
|
rlSetTexture(map.data.textures[map.data.texInfo[map.data.faces[i].texinfoId].textureId].glTextureID);
|
||||||
|
rlBegin(RL_QUADS); // textures don't work well with triangles in rlgl for some reason
|
||||||
|
|
||||||
|
// rlTexCoord2f(0, 0);
|
||||||
|
rlTexCoord2f(
|
||||||
|
(Vector3DotProduct(MPFW::Quake::Maps::RLVec2qVec(vertices[0]), map.data.texInfo[map.data.faces[i].texinfoId].vectorS) + map.data.texInfo[map.data.faces[i].texinfoId].distS) / map.data.textures[map.data.texInfo[map.data.faces[i].texinfoId].textureId].width,
|
||||||
|
(Vector3DotProduct(MPFW::Quake::Maps::RLVec2qVec(vertices[0]), map.data.texInfo[map.data.faces[i].texinfoId].vectorT) + map.data.texInfo[map.data.faces[i].texinfoId].distT) / map.data.textures[map.data.texInfo[map.data.faces[i].texinfoId].textureId].height
|
||||||
|
);
|
||||||
|
rlVertex3f(vertices[0].x, vertices[0].y, vertices[0].z);
|
||||||
|
// rlTexCoord2f(0, 1);
|
||||||
|
rlTexCoord2f(
|
||||||
|
(Vector3DotProduct(MPFW::Quake::Maps::RLVec2qVec(vertices[j]), map.data.texInfo[map.data.faces[i].texinfoId].vectorS) + map.data.texInfo[map.data.faces[i].texinfoId].distS) / map.data.textures[map.data.texInfo[map.data.faces[i].texinfoId].textureId].width,
|
||||||
|
(Vector3DotProduct(MPFW::Quake::Maps::RLVec2qVec(vertices[j]), map.data.texInfo[map.data.faces[i].texinfoId].vectorT) + map.data.texInfo[map.data.faces[i].texinfoId].distT) / map.data.textures[map.data.texInfo[map.data.faces[i].texinfoId].textureId].height
|
||||||
|
);
|
||||||
|
rlVertex3f(vertices[j].x, vertices[j].y, vertices[j].z);
|
||||||
|
// rlTexCoord2f(1, 1);
|
||||||
|
rlTexCoord2f(
|
||||||
|
(Vector3DotProduct(MPFW::Quake::Maps::RLVec2qVec(vertices[j - 1]), map.data.texInfo[map.data.faces[i].texinfoId].vectorS) + map.data.texInfo[map.data.faces[i].texinfoId].distS) / map.data.textures[map.data.texInfo[map.data.faces[i].texinfoId].textureId].width,
|
||||||
|
(Vector3DotProduct(MPFW::Quake::Maps::RLVec2qVec(vertices[j - 1]), map.data.texInfo[map.data.faces[i].texinfoId].vectorT) + map.data.texInfo[map.data.faces[i].texinfoId].distT) / map.data.textures[map.data.texInfo[map.data.faces[i].texinfoId].textureId].height
|
||||||
|
);
|
||||||
|
rlVertex3f(vertices[j - 1].x, vertices[j - 1].y, vertices[j - 1].z);
|
||||||
|
rlVertex3f(vertices[j - 1].x, vertices[j - 1].y, vertices[j - 1].z);
|
||||||
|
|
||||||
|
rlEnd();
|
||||||
|
// rlColor4ub(255, 255, 255, 255);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
int i = indivFace;
|
||||||
|
|
||||||
|
std::vector<Vector3> vertices;
|
||||||
|
|
||||||
|
rlColor4ub(colors[i].r, colors[i].g, colors[i].b, colors[i].a);
|
||||||
|
rlBegin(RL_TRIANGLES);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Vector3 a = MPFW::Quake::Maps::qVec2RLVec(map.data.vertices[map.data.edges[abs(map.data.ledges[map.data.faces[i].ledgeId])].vertex0]);
|
||||||
|
|
||||||
|
|
||||||
|
for (int k = 0; k < map.data.faces[i].ledgeNum; k++) {
|
||||||
|
if (map.data.ledges[map.data.faces[i].ledgeId + k] < 0) {
|
||||||
|
vertices.push_back(MPFW::Quake::Maps::qVec2RLVec(
|
||||||
|
map.data.vertices[
|
||||||
|
map.data.edges[
|
||||||
|
abs(
|
||||||
|
map.data.ledges[
|
||||||
|
map.data.faces[i].ledgeId + k
|
||||||
|
]
|
||||||
|
)
|
||||||
|
].vertex1
|
||||||
|
]
|
||||||
|
));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
vertices.push_back(MPFW::Quake::Maps::qVec2RLVec(
|
||||||
|
map.data.vertices[
|
||||||
|
map.data.edges[
|
||||||
|
abs(
|
||||||
|
map.data.ledges[
|
||||||
|
map.data.faces[i].ledgeId + k
|
||||||
|
]
|
||||||
|
)
|
||||||
|
].vertex0
|
||||||
|
]
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
DrawLine3D(MPFW::Quake::Maps::qVec2RLVec(
|
||||||
|
map.data.vertices[
|
||||||
|
map.data.edges[
|
||||||
|
abs(
|
||||||
|
map.data.ledges[
|
||||||
|
map.data.faces[i].ledgeId + k
|
||||||
|
]
|
||||||
|
)
|
||||||
|
].vertex0
|
||||||
|
]
|
||||||
|
),
|
||||||
|
|
||||||
|
MPFW::Quake::Maps::qVec2RLVec(
|
||||||
|
map.data.vertices[
|
||||||
|
map.data.edges[
|
||||||
|
abs(
|
||||||
|
map.data.ledges[
|
||||||
|
map.data.faces[i].ledgeId + k
|
||||||
|
]
|
||||||
|
)
|
||||||
|
].vertex1
|
||||||
|
]
|
||||||
|
), RED);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
rlVertex3f(vertices[0].x, vertices[0].y, vertices[0].z);
|
||||||
|
|
||||||
|
rlVertex3f(vertices[2].x, vertices[2].y, vertices[2].z);
|
||||||
|
rlVertex3f(vertices[1].x, vertices[1].y, vertices[1].z);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
rlEnd();
|
||||||
|
rlColor4f(255, 255, 255, 255);
|
||||||
|
}
|
||||||
|
EndMode3D();
|
||||||
|
DrawFPS(0, 0);
|
||||||
|
if (indivFaceMode) {
|
||||||
|
|
||||||
|
|
||||||
|
std::vector<Vector3> vertices;
|
||||||
|
{
|
||||||
|
int i = indivFace;
|
||||||
|
|
||||||
|
|
||||||
|
rlColor4ub(colors[i].r, colors[i].g, colors[i].b, colors[i].a);
|
||||||
|
rlBegin(RL_TRIANGLES);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Vector3 a = MPFW::Quake::Maps::qVec2RLVec(map.data.vertices[map.data.edges[abs(map.data.ledges[map.data.faces[i].ledgeId])].vertex0]);
|
||||||
|
|
||||||
|
|
||||||
|
for (int k = 0; k < map.data.faces[i].ledgeNum; k++) {
|
||||||
|
if (map.data.ledges[map.data.faces[i].ledgeId + k] < 0) {
|
||||||
|
vertices.push_back(MPFW::Quake::Maps::qVec2RLVec(
|
||||||
|
map.data.vertices[
|
||||||
|
map.data.edges[
|
||||||
|
abs(
|
||||||
|
map.data.ledges[
|
||||||
|
map.data.faces[i].ledgeId + k
|
||||||
|
]
|
||||||
|
)
|
||||||
|
].vertex1
|
||||||
|
]
|
||||||
|
));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
vertices.push_back(MPFW::Quake::Maps::qVec2RLVec(
|
||||||
|
map.data.vertices[
|
||||||
|
map.data.edges[
|
||||||
|
abs(
|
||||||
|
map.data.ledges[
|
||||||
|
map.data.faces[i].ledgeId + k
|
||||||
|
]
|
||||||
|
)
|
||||||
|
].vertex0
|
||||||
|
]
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
DrawText(TextFormat("Current face: %i", indivFace), 0, 30, 30, BLACK);
|
||||||
|
for (int i = 0; i < vertices.size(); i++) {
|
||||||
|
DrawText(TextFormat("Vertex %i : %f %f %f", i, vertices[i].x, vertices[i].y, vertices[i].z), 0, 60 + 10 * i, 10, BLACK);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (consoleOn) {
|
||||||
|
DrawRectangle(0, 0, GetScreenWidth(), GetScreenHeight() / 2, {0,0,0,200});
|
||||||
|
DrawTextEx(robotoMonoRegular, cmdH.logStr.c_str(), { 0, GetScreenHeight() / 2 - MeasureTextEx(robotoMonoRegular, cmdH.logStr.c_str(), 20, 0).y }, 20, 0, WHITE);
|
||||||
|
|
||||||
|
DrawRectangle(0, GetScreenHeight() / 2, GetScreenWidth(), 20, { 0,0,0,200 });
|
||||||
|
DrawLine(0, GetScreenHeight() / 2, GetScreenWidth(), GetScreenHeight() / 2, GOLD);
|
||||||
|
DrawTextEx(robotoMonoRegular, cmdBuf.c_str(), { 0, (float)GetScreenHeight() / 2 }, 20, 0, GRAY);
|
||||||
|
|
||||||
|
int key = GetCharPressed();
|
||||||
|
while (key > 0) {
|
||||||
|
if ((key >= 32) && (key <= 126)) {
|
||||||
|
cmdBuf += (char)key;
|
||||||
|
|
||||||
|
}
|
||||||
|
key = GetCharPressed();
|
||||||
|
}
|
||||||
|
if (IsKeyPressed(KEY_BACKSPACE) && cmdBuf != "") cmdBuf.pop_back();
|
||||||
|
|
||||||
|
if (IsKeyPressed(KEY_ENTER)) {
|
||||||
|
cmdH.Run(cmdBuf);
|
||||||
|
cmdBuf = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
EndDrawing();
|
||||||
|
}
|
||||||
|
|
||||||
|
CloseWindow();
|
||||||
|
}
|
159
mpfw/mpfw.vcxproj
Normal file
159
mpfw/mpfw.vcxproj
Normal file
@ -0,0 +1,159 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
|
<ItemGroup Label="ProjectConfigurations">
|
||||||
|
<ProjectConfiguration Include="Debug|Win32">
|
||||||
|
<Configuration>Debug</Configuration>
|
||||||
|
<Platform>Win32</Platform>
|
||||||
|
</ProjectConfiguration>
|
||||||
|
<ProjectConfiguration Include="Release|Win32">
|
||||||
|
<Configuration>Release</Configuration>
|
||||||
|
<Platform>Win32</Platform>
|
||||||
|
</ProjectConfiguration>
|
||||||
|
<ProjectConfiguration Include="Debug|x64">
|
||||||
|
<Configuration>Debug</Configuration>
|
||||||
|
<Platform>x64</Platform>
|
||||||
|
</ProjectConfiguration>
|
||||||
|
<ProjectConfiguration Include="Release|x64">
|
||||||
|
<Configuration>Release</Configuration>
|
||||||
|
<Platform>x64</Platform>
|
||||||
|
</ProjectConfiguration>
|
||||||
|
</ItemGroup>
|
||||||
|
<PropertyGroup Label="Globals">
|
||||||
|
<VCProjectVersion>17.0</VCProjectVersion>
|
||||||
|
<Keyword>Win32Proj</Keyword>
|
||||||
|
<ProjectGuid>{fb36e71a-46ab-4ece-9438-e683c4208fb1}</ProjectGuid>
|
||||||
|
<RootNamespace>mpfw</RootNamespace>
|
||||||
|
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
|
||||||
|
</PropertyGroup>
|
||||||
|
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
|
||||||
|
<ConfigurationType>Application</ConfigurationType>
|
||||||
|
<UseDebugLibraries>true</UseDebugLibraries>
|
||||||
|
<PlatformToolset>v143</PlatformToolset>
|
||||||
|
<CharacterSet>Unicode</CharacterSet>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
|
||||||
|
<ConfigurationType>Application</ConfigurationType>
|
||||||
|
<UseDebugLibraries>false</UseDebugLibraries>
|
||||||
|
<PlatformToolset>v143</PlatformToolset>
|
||||||
|
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||||
|
<CharacterSet>Unicode</CharacterSet>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
|
||||||
|
<ConfigurationType>Application</ConfigurationType>
|
||||||
|
<UseDebugLibraries>true</UseDebugLibraries>
|
||||||
|
<PlatformToolset>v143</PlatformToolset>
|
||||||
|
<CharacterSet>Unicode</CharacterSet>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
|
||||||
|
<ConfigurationType>Application</ConfigurationType>
|
||||||
|
<UseDebugLibraries>false</UseDebugLibraries>
|
||||||
|
<PlatformToolset>v143</PlatformToolset>
|
||||||
|
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||||
|
<CharacterSet>Unicode</CharacterSet>
|
||||||
|
</PropertyGroup>
|
||||||
|
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||||
|
<ImportGroup Label="ExtensionSettings">
|
||||||
|
</ImportGroup>
|
||||||
|
<ImportGroup Label="Shared">
|
||||||
|
</ImportGroup>
|
||||||
|
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||||
|
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||||
|
</ImportGroup>
|
||||||
|
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||||
|
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||||
|
</ImportGroup>
|
||||||
|
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||||
|
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||||
|
</ImportGroup>
|
||||||
|
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||||
|
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||||
|
</ImportGroup>
|
||||||
|
<PropertyGroup Label="UserMacros" />
|
||||||
|
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||||
|
<ClCompile>
|
||||||
|
<WarningLevel>Level3</WarningLevel>
|
||||||
|
<SDLCheck>true</SDLCheck>
|
||||||
|
<PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||||
|
<ConformanceMode>true</ConformanceMode>
|
||||||
|
</ClCompile>
|
||||||
|
<Link>
|
||||||
|
<SubSystem>Console</SubSystem>
|
||||||
|
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||||
|
</Link>
|
||||||
|
</ItemDefinitionGroup>
|
||||||
|
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||||
|
<ClCompile>
|
||||||
|
<WarningLevel>Level3</WarningLevel>
|
||||||
|
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||||
|
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||||
|
<SDLCheck>true</SDLCheck>
|
||||||
|
<PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||||
|
<ConformanceMode>true</ConformanceMode>
|
||||||
|
</ClCompile>
|
||||||
|
<Link>
|
||||||
|
<SubSystem>Console</SubSystem>
|
||||||
|
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||||
|
<OptimizeReferences>true</OptimizeReferences>
|
||||||
|
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||||
|
</Link>
|
||||||
|
</ItemDefinitionGroup>
|
||||||
|
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||||
|
<ClCompile>
|
||||||
|
<WarningLevel>Level3</WarningLevel>
|
||||||
|
<SDLCheck>true</SDLCheck>
|
||||||
|
<PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||||
|
<ConformanceMode>true</ConformanceMode>
|
||||||
|
<LanguageStandard>stdcpplatest</LanguageStandard>
|
||||||
|
<AdditionalIncludeDirectories>$(SOLUTIONDIR)deps/include</AdditionalIncludeDirectories>
|
||||||
|
</ClCompile>
|
||||||
|
<Link>
|
||||||
|
<SubSystem>Console</SubSystem>
|
||||||
|
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||||
|
<AdditionalLibraryDirectories>$(SOLUTIONDIR)deps/lib</AdditionalLibraryDirectories>
|
||||||
|
<AdditionalDependencies>raylib.lib;winmm.lib;gdi32.lib;$(CoreLibraryDependencies);%(AdditionalDependencies)</AdditionalDependencies>
|
||||||
|
<IgnoreSpecificDefaultLibraries>MSVCRT</IgnoreSpecificDefaultLibraries>
|
||||||
|
</Link>
|
||||||
|
</ItemDefinitionGroup>
|
||||||
|
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||||
|
<ClCompile>
|
||||||
|
<WarningLevel>Level3</WarningLevel>
|
||||||
|
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||||
|
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||||
|
<SDLCheck>true</SDLCheck>
|
||||||
|
<PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||||
|
<ConformanceMode>true</ConformanceMode>
|
||||||
|
<LanguageStandard>stdcpplatest</LanguageStandard>
|
||||||
|
<AdditionalIncludeDirectories>$(SOLUTIONDIR)deps/include</AdditionalIncludeDirectories>
|
||||||
|
</ClCompile>
|
||||||
|
<Link>
|
||||||
|
<SubSystem>Console</SubSystem>
|
||||||
|
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||||
|
<OptimizeReferences>true</OptimizeReferences>
|
||||||
|
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||||
|
<AdditionalLibraryDirectories>$(SOLUTIONDIR)deps/lib</AdditionalLibraryDirectories>
|
||||||
|
<AdditionalDependencies>raylib.lib;winmm.lib;gdi32.lib;$(CoreLibraryDependencies);%(AdditionalDependencies)</AdditionalDependencies>
|
||||||
|
<IgnoreSpecificDefaultLibraries>MSVCRT</IgnoreSpecificDefaultLibraries>
|
||||||
|
</Link>
|
||||||
|
</ItemDefinitionGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<ClCompile Include="main.cpp" />
|
||||||
|
<ClCompile Include="MPFW_Console.cpp" />
|
||||||
|
<ClCompile Include="MPFW_CVars.cpp" />
|
||||||
|
<ClCompile Include="MPFW_HL.cpp" />
|
||||||
|
<ClCompile Include="MPFW_Quake.cpp" />
|
||||||
|
<ClCompile Include="MPFW_Utils.cpp" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<ClInclude Include="MPFW_Console.h" />
|
||||||
|
<ClInclude Include="MPFW_CVars.h" />
|
||||||
|
<ClInclude Include="MPFW_HL.h" />
|
||||||
|
<ClInclude Include="MPFW_MPFWMF.h" />
|
||||||
|
<ClInclude Include="MPFW_Quake.h" />
|
||||||
|
<ClInclude Include="MPFW_UI.h" />
|
||||||
|
<ClInclude Include="MPFW_Utils.h" />
|
||||||
|
</ItemGroup>
|
||||||
|
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||||
|
<ImportGroup Label="ExtensionTargets">
|
||||||
|
</ImportGroup>
|
||||||
|
</Project>
|
60
mpfw/mpfw.vcxproj.filters
Normal file
60
mpfw/mpfw.vcxproj.filters
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
|
<ItemGroup>
|
||||||
|
<Filter Include="Source Files">
|
||||||
|
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
|
||||||
|
<Extensions>cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
|
||||||
|
</Filter>
|
||||||
|
<Filter Include="Header Files">
|
||||||
|
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
|
||||||
|
<Extensions>h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd</Extensions>
|
||||||
|
</Filter>
|
||||||
|
<Filter Include="Resource Files">
|
||||||
|
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
|
||||||
|
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
|
||||||
|
</Filter>
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<ClCompile Include="main.cpp">
|
||||||
|
<Filter>Source Files</Filter>
|
||||||
|
</ClCompile>
|
||||||
|
<ClCompile Include="MPFW_Utils.cpp">
|
||||||
|
<Filter>Source Files</Filter>
|
||||||
|
</ClCompile>
|
||||||
|
<ClCompile Include="MPFW_HL.cpp">
|
||||||
|
<Filter>Source Files</Filter>
|
||||||
|
</ClCompile>
|
||||||
|
<ClCompile Include="MPFW_Quake.cpp">
|
||||||
|
<Filter>Source Files</Filter>
|
||||||
|
</ClCompile>
|
||||||
|
<ClCompile Include="MPFW_Console.cpp">
|
||||||
|
<Filter>Source Files</Filter>
|
||||||
|
</ClCompile>
|
||||||
|
<ClCompile Include="MPFW_CVars.cpp">
|
||||||
|
<Filter>Source Files</Filter>
|
||||||
|
</ClCompile>
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<ClInclude Include="MPFW_HL.h">
|
||||||
|
<Filter>Header Files</Filter>
|
||||||
|
</ClInclude>
|
||||||
|
<ClInclude Include="MPFW_Utils.h">
|
||||||
|
<Filter>Header Files</Filter>
|
||||||
|
</ClInclude>
|
||||||
|
<ClInclude Include="MPFW_MPFWMF.h">
|
||||||
|
<Filter>Header Files</Filter>
|
||||||
|
</ClInclude>
|
||||||
|
<ClInclude Include="MPFW_Quake.h">
|
||||||
|
<Filter>Header Files</Filter>
|
||||||
|
</ClInclude>
|
||||||
|
<ClInclude Include="MPFW_Console.h">
|
||||||
|
<Filter>Header Files</Filter>
|
||||||
|
</ClInclude>
|
||||||
|
<ClInclude Include="MPFW_CVars.h">
|
||||||
|
<Filter>Header Files</Filter>
|
||||||
|
</ClInclude>
|
||||||
|
<ClInclude Include="MPFW_UI.h">
|
||||||
|
<Filter>Header Files</Filter>
|
||||||
|
</ClInclude>
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
11
mpfw/mpfw.vcxproj.user
Normal file
11
mpfw/mpfw.vcxproj.user
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||||
|
<LocalDebuggerWorkingDirectory>$(SolutionDir)/gameenv</LocalDebuggerWorkingDirectory>
|
||||||
|
<DebuggerFlavor>WindowsLocalDebugger</DebuggerFlavor>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||||
|
<LocalDebuggerWorkingDirectory>$(SolutionDir)/gameenv</LocalDebuggerWorkingDirectory>
|
||||||
|
<DebuggerFlavor>WindowsLocalDebugger</DebuggerFlavor>
|
||||||
|
</PropertyGroup>
|
||||||
|
</Project>
|
23
mpfw_server/Config.h
Normal file
23
mpfw_server/Config.h
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
namespace Config {
|
||||||
|
|
||||||
|
|
||||||
|
struct Config {
|
||||||
|
unsigned char maxPlayers = 16; // maximum players that can fit on the server.
|
||||||
|
// original quake specs say up to 16, glquake slist
|
||||||
|
// can show up to 255 (standard calls for uchar).
|
||||||
|
|
||||||
|
std::string serverName = "MPFWTESTSRV"; // left column in slist
|
||||||
|
std::string serverBanner = "oh yeah this a mfing server!";
|
||||||
|
std::string ip = "192.168.56.1";
|
||||||
|
char unknownMagic[2] = {'x', 'D'}; // unknown short sent at the end of accepted connection request replies
|
||||||
|
|
||||||
|
int udpControl = 26000; // port to which clients will connect for control
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
}
|
534
mpfw_server/NetQuake.cpp
Normal file
534
mpfw_server/NetQuake.cpp
Normal file
@ -0,0 +1,534 @@
|
|||||||
|
#include "NetQuake.h"
|
||||||
|
#include <iostream> // basic io
|
||||||
|
#include <vector> // dynamic arrays
|
||||||
|
#include <map> // keyval maps
|
||||||
|
#include <asio.hpp>
|
||||||
|
#include <asio/ts/buffer.hpp>
|
||||||
|
#include <asio/ts/internet.hpp>
|
||||||
|
#include <print>
|
||||||
|
#include <string>
|
||||||
|
#include <fstream>
|
||||||
|
#include <excpt.h>
|
||||||
|
|
||||||
|
NetQuake::SerializedGenericPacket NetQuake::Serialize(NetworkGenericPacket ngp)
|
||||||
|
{
|
||||||
|
SerializedGenericPacket sgp;
|
||||||
|
|
||||||
|
|
||||||
|
unsigned short typeAsShort;
|
||||||
|
|
||||||
|
typeAsShort = (ngp.type[0] << 8) + ngp.type[1];
|
||||||
|
sgp.type = (PacketType)typeAsShort;
|
||||||
|
sgp.length = (ngp.length[0] << 8) + ngp.length[1];
|
||||||
|
|
||||||
|
sgp.remainder = ngp.remainder;
|
||||||
|
|
||||||
|
return sgp;
|
||||||
|
}
|
||||||
|
|
||||||
|
NetQuake::GenericGamePacket NetQuake::toGenericGamePacket(SerializedGenericPacket sgp)
|
||||||
|
{
|
||||||
|
GenericGamePacket retval;
|
||||||
|
try {
|
||||||
|
|
||||||
|
int currentPacketLength = 0;
|
||||||
|
int currentPacketAt = 0;
|
||||||
|
bool inString = false;
|
||||||
|
for (int i = 0; i < sgp.remainder.size(); i++) {
|
||||||
|
if (currentPacketLength == 0 && !inString) {
|
||||||
|
if (sgp.remainder[i] == CLIENT_MESSAGE_NOOP) {
|
||||||
|
ClientMessage msg;
|
||||||
|
msg.type = CLIENT_MESSAGE_NOOP;
|
||||||
|
retval.messageBlock.messages.push_back(msg);
|
||||||
|
}
|
||||||
|
if (sgp.remainder[i] == CLIENT_MESSAGE_KEEPALIVE) {
|
||||||
|
ClientMessage msg;
|
||||||
|
msg.type = CLIENT_MESSAGE_NOOP;
|
||||||
|
retval.messageBlock.messages.push_back(msg);
|
||||||
|
}
|
||||||
|
if (sgp.remainder[i] == CLIENT_MESSAGE_MOVEMENT) {
|
||||||
|
ClientMessage msg;
|
||||||
|
msg.type = CLIENT_MESSAGE_MOVEMENT;
|
||||||
|
retval.messageBlock.messages.push_back(msg);
|
||||||
|
currentPacketLength = 15;
|
||||||
|
}
|
||||||
|
if (sgp.remainder[i] == CLIENT_MESSAGE_CONSOLE) {
|
||||||
|
ClientMessage msg;
|
||||||
|
msg.type = CLIENT_MESSAGE_CONSOLE;
|
||||||
|
retval.messageBlock.messages.push_back(msg);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (inString) {
|
||||||
|
if (sgp.remainder[i] == 0x00) inString = false;
|
||||||
|
else {
|
||||||
|
retval.messageBlock.messages[retval.messageBlock.messages.size() - 1].data += sgp.remainder[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
retval.messageBlock.messages[retval.messageBlock.messages.size() - 1].data += sgp.remainder[i];
|
||||||
|
currentPacketAt++;
|
||||||
|
if (currentPacketAt == currentPacketLength) {
|
||||||
|
currentPacketLength = 0;
|
||||||
|
currentPacketAt = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
retval.valid = true;
|
||||||
|
}
|
||||||
|
catch (std::exception& e) {
|
||||||
|
std::println("Something went wrong in serialized generic packet to generic game packet conversion: {}", e.what());
|
||||||
|
}
|
||||||
|
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string NetQuake::Serialize(ServerInfoReply sirp)
|
||||||
|
{
|
||||||
|
std::string retval = "";
|
||||||
|
|
||||||
|
using namespace std::string_literals;
|
||||||
|
retval += CONTROL_SERVER_INFO_REPLY;
|
||||||
|
retval += sirp.address.ip + ":";
|
||||||
|
retval += std::to_string(sirp.address.port) + "\0"s;
|
||||||
|
retval += sirp.hostname + "\0"s;
|
||||||
|
retval += sirp.levelname + "\0"s;
|
||||||
|
retval += sirp.currentPlayers;
|
||||||
|
retval += sirp.maxPlayers;
|
||||||
|
retval += sirp.netVersion;
|
||||||
|
|
||||||
|
short retvalsize = retval.size() + 4;
|
||||||
|
unsigned char retvalsizechar[2];
|
||||||
|
|
||||||
|
retvalsizechar[0] = retvalsize & 0xff;
|
||||||
|
retvalsizechar[1] = (retvalsize >> 8) & 0xff;
|
||||||
|
|
||||||
|
|
||||||
|
std::string fretval = "\x80";
|
||||||
|
fretval += "\0"s;
|
||||||
|
fretval += retvalsizechar[1];
|
||||||
|
fretval += retvalsizechar[0];
|
||||||
|
fretval += retval;
|
||||||
|
return fretval;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string NetQuake::Serialize(ConnectionRequestReply crr)
|
||||||
|
{
|
||||||
|
std::string retval = "";
|
||||||
|
using namespace std::string_literals;
|
||||||
|
|
||||||
|
|
||||||
|
if (crr.reject) {
|
||||||
|
retval = "\x82" + crr.reason + "\0"s;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
|
||||||
|
unsigned char portchar[2];
|
||||||
|
portchar[1] = crr.port & 0xff;
|
||||||
|
portchar[0] = (crr.port >> 8) & 0xff;
|
||||||
|
retval = "\x81"s;
|
||||||
|
retval += portchar[1];
|
||||||
|
retval += portchar[0];
|
||||||
|
retval += (unsigned char)crr.unknown[0];
|
||||||
|
retval += (unsigned char)crr.unknown[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
short retvalsize = retval.size() + 4;
|
||||||
|
unsigned char retvalsizechar[2];
|
||||||
|
|
||||||
|
retvalsizechar[0] = retvalsize & 0xff;
|
||||||
|
retvalsizechar[1] = (retvalsize >> 8) & 0xff;
|
||||||
|
|
||||||
|
std::string fretval = "\x80";
|
||||||
|
fretval += "\0"s;
|
||||||
|
fretval += retvalsizechar[1];
|
||||||
|
fretval += retvalsizechar[0];
|
||||||
|
fretval += retval;
|
||||||
|
return fretval;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string NetQuake::AcknowledgePacket(int packet)
|
||||||
|
{
|
||||||
|
std::string retval;
|
||||||
|
using namespace std::string_literals;
|
||||||
|
retval += reinterpret_cast<char*>(packet);
|
||||||
|
short retvalsize = retval.size() + 4;
|
||||||
|
unsigned char retvalsizechar[2];
|
||||||
|
|
||||||
|
retvalsizechar[0] = retvalsize & 0xff;
|
||||||
|
retvalsizechar[1] = (retvalsize >> 8) & 0xff;
|
||||||
|
|
||||||
|
std::string fretval = "\0"s;
|
||||||
|
fretval += "\x10"s;
|
||||||
|
fretval += retvalsizechar[1];
|
||||||
|
fretval += retvalsizechar[0];
|
||||||
|
fretval += retval;
|
||||||
|
return fretval;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NetQuake::NetQuake_ControlServer(Internal::InternalRequestConsole* internalRequestConsole, Config::Config* config, bool* serverRunning)
|
||||||
|
{
|
||||||
|
|
||||||
|
asio::io_context io_context;
|
||||||
|
asio::ip::udp::socket socket(io_context, asio::ip::udp::endpoint(asio::ip::udp::v4(), config->udpControl));
|
||||||
|
std::print("Started control server on {}\n", config->udpControl);
|
||||||
|
|
||||||
|
NetQuake::ServerInfoReply sir;
|
||||||
|
sir.address.ip = config->ip;
|
||||||
|
sir.address.port = config->udpControl;
|
||||||
|
sir.currentPlayers = 0;
|
||||||
|
sir.maxPlayers = config->maxPlayers;
|
||||||
|
|
||||||
|
sir.hostname = config->serverName;
|
||||||
|
sir.levelname = "start";
|
||||||
|
sir.netVersion = NetQuake::QGOLD;
|
||||||
|
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
asio::ip::udp::endpoint remote_endpoint;
|
||||||
|
std::array<char, 1024> recv_buf;
|
||||||
|
|
||||||
|
|
||||||
|
socket.receive_from(asio::buffer(recv_buf), remote_endpoint);
|
||||||
|
|
||||||
|
NetQuake::NetworkGenericPacket gpac;
|
||||||
|
|
||||||
|
gpac.type[0] = recv_buf[0];
|
||||||
|
gpac.type[1] = recv_buf[1];
|
||||||
|
gpac.length[0] = recv_buf[2];
|
||||||
|
gpac.length[1] = recv_buf[3];
|
||||||
|
for (int i = 4; i < recv_buf.size(); i++) {
|
||||||
|
gpac.remainder += recv_buf[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
NetQuake::SerializedGenericPacket sgpac = NetQuake::Serialize(gpac);
|
||||||
|
|
||||||
|
std::cout << "new packet\n";
|
||||||
|
switch (sgpac.type) {
|
||||||
|
|
||||||
|
case NetQuake::NQ_CONTROL_PACKET:
|
||||||
|
{
|
||||||
|
NetQuake::GenericControlPacket gcpac(sgpac);
|
||||||
|
|
||||||
|
std::cout << "GenericControlPacket: " << gcpac.opcode << "\n";
|
||||||
|
|
||||||
|
// the 0x01 i had so much trouble getting through
|
||||||
|
if (gcpac.opcode == NetQuake::CONTROL_CONNECTION_REQUEST) {
|
||||||
|
std::cout << "Connection Request\n";
|
||||||
|
|
||||||
|
int endpointRequestId = internalRequestConsole->newEndpointRequest(remote_endpoint);
|
||||||
|
|
||||||
|
while (!internalRequestConsole->getEndpointRequest(endpointRequestId).ready) {
|
||||||
|
// waiting for endpoint request to go through
|
||||||
|
}
|
||||||
|
|
||||||
|
Internal::NewEndpointRequestReply nerr = internalRequestConsole->getEndpointRequest(endpointRequestId);
|
||||||
|
|
||||||
|
NetQuake::ConnectionRequestReply crr;
|
||||||
|
|
||||||
|
crr.reject = nerr.error;
|
||||||
|
crr.reason = nerr.reason;
|
||||||
|
crr.port = nerr.port;
|
||||||
|
|
||||||
|
crr.unknown[0] = config->unknownMagic[0];
|
||||||
|
crr.unknown[1] = config->unknownMagic[1];
|
||||||
|
|
||||||
|
asio::error_code e;
|
||||||
|
socket.send_to(asio::buffer(NetQuake::Serialize(crr)), remote_endpoint, 0, e);
|
||||||
|
/*NetQuake::ConnectionRequestReply crr;
|
||||||
|
|
||||||
|
crr.reject = false;
|
||||||
|
crr.port = 26000;
|
||||||
|
crr.unknown[0] = config->unknownMagic[0];
|
||||||
|
crr.unknown[1] = config->unknownMagic[1];
|
||||||
|
|
||||||
|
asio::error_code e;
|
||||||
|
socket.send_to(asio::buffer(NetQuake::Serialize(crr)), remote_endpoint, 0, e);
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
if (gcpac.opcode == NetQuake::CONTROL_SERVER_INFO_REQUEST) {
|
||||||
|
NetQuake::ServerInfoRequest sirqpac(gcpac);
|
||||||
|
|
||||||
|
std::cout << "ServerInfoRequest: server info request for game \"" << sirqpac.gameName << "\", netversion " << sirqpac.netVersion << "\n";
|
||||||
|
|
||||||
|
std::string serializedTemplate = NetQuake::Serialize(sir);
|
||||||
|
|
||||||
|
std::ofstream templatestream("template.txt", std::ios::binary);
|
||||||
|
templatestream << serializedTemplate;
|
||||||
|
templatestream.close();
|
||||||
|
asio::error_code e;
|
||||||
|
socket.send_to(asio::buffer(serializedTemplate), remote_endpoint, 0, e);
|
||||||
|
std::cout << e.message() << "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
std::cout << "[WARNING] Unknown or unsupported packet came through.\n";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// std::cout << gcpac.opcode << "\n";
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void NetQuake::NetQuake_EndpointServer(Config::Config* config, int port, asio::ip::udp::endpoint startRemote)
|
||||||
|
{
|
||||||
|
asio::io_context io_context;
|
||||||
|
asio::error_code e;
|
||||||
|
|
||||||
|
|
||||||
|
asio::ip::udp::socket socket(io_context, asio::ip::udp::endpoint(asio::ip::udp::v4(), port));
|
||||||
|
|
||||||
|
socket.connect(startRemote);
|
||||||
|
|
||||||
|
std::print("New endpoint server started on {}.\nClient Start Info: {} | {}\n", port, startRemote.address().to_string(), startRemote.port());
|
||||||
|
|
||||||
|
{ // startup block
|
||||||
|
ServerMessageBlock smb;
|
||||||
|
smb.type = NQ_MESSAGE_BLOCK_END;
|
||||||
|
smb.order = 100;
|
||||||
|
ServerMessages::Print serverBanner;
|
||||||
|
|
||||||
|
serverBanner.text = config->serverBanner;
|
||||||
|
smb.serverMessages.push_back(&serverBanner);
|
||||||
|
|
||||||
|
|
||||||
|
ServerMessages::ServerInfo serverInfo;
|
||||||
|
|
||||||
|
serverInfo.mapName = "start";
|
||||||
|
serverInfo.maxClients = config->maxPlayers;
|
||||||
|
serverInfo.multi = 1;
|
||||||
|
serverInfo.numModels = 0;
|
||||||
|
serverInfo.numSounds = 0;
|
||||||
|
serverInfo.serverVersion = 15;
|
||||||
|
|
||||||
|
smb.serverMessages.push_back(&serverInfo);
|
||||||
|
|
||||||
|
ServerMessages::SignOn signOn;
|
||||||
|
signOn.signon = 1; // prespawn(2=light,3=render)
|
||||||
|
smb.serverMessages.push_back(&signOn);
|
||||||
|
|
||||||
|
std::print("Tried sending server banner to client.\n");
|
||||||
|
|
||||||
|
socket.send_to(asio::buffer(smb.Serialize()), startRemote, 0, e);
|
||||||
|
std::ofstream smbattempt("smb.txt");
|
||||||
|
smbattempt << smb.Serialize();
|
||||||
|
smbattempt.close();
|
||||||
|
|
||||||
|
|
||||||
|
std::print("Server banner should be sent.\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
asio::ip::udp::endpoint rec;
|
||||||
|
std::string recv_buf;
|
||||||
|
socket.receive_from(asio::buffer(recv_buf), rec);
|
||||||
|
std::cout << "packet on secondary line " << port << "\n";
|
||||||
|
std::cout << "received packet size: " << recv_buf.size() << "\n";
|
||||||
|
if(recv_buf.size() > 4) {
|
||||||
|
try {
|
||||||
|
NetQuake::NetworkGenericPacket ngp;
|
||||||
|
ngp.type[0] = recv_buf[0];
|
||||||
|
ngp.type[1] = recv_buf[1];
|
||||||
|
ngp.length[0] = recv_buf[2];
|
||||||
|
ngp.length[1] = recv_buf[3];
|
||||||
|
for (int i = 4; i < recv_buf.size(); i++) {
|
||||||
|
ngp.remainder += recv_buf[i];
|
||||||
|
}
|
||||||
|
SerializedGenericPacket sgpac = Serialize(ngp);
|
||||||
|
GenericGamePacket ggp = toGenericGamePacket(sgpac);
|
||||||
|
|
||||||
|
|
||||||
|
if (ggp.type == NQ_MESSAGE_BLOCK_CHUNK || ggp.type == NQ_MESSAGE_BLOCK_END) {
|
||||||
|
socket.send_to(asio::buffer(AcknowledgePacket(ggp.packetNumber)), rec);
|
||||||
|
std::println("Server acknowledged packet {}", ggp.packetNumber);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (...) {
|
||||||
|
std::println("Error on packet on {}", port);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
std::println("Weird packet received... :shrug:");
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
NetQuake::GenericControlPacket::GenericControlPacket(SerializedGenericPacket sgp)
|
||||||
|
{
|
||||||
|
|
||||||
|
if (sgp.type != NQ_CONTROL_PACKET) {
|
||||||
|
throw std::runtime_error("Wrong packet type given to generic control packet constructor.");
|
||||||
|
}
|
||||||
|
|
||||||
|
opcode = (Control_OpCode)sgp.remainder[0];
|
||||||
|
|
||||||
|
remainder = sgp.remainder;
|
||||||
|
remainder.erase(0, 1);
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
NetQuake::ServerInfoRequest::ServerInfoRequest(GenericControlPacket gcp)
|
||||||
|
{
|
||||||
|
if (gcp.opcode != CONTROL_SERVER_INFO_REQUEST) {
|
||||||
|
throw std::runtime_error("Wrong packet type given to server info request constructor.");
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string toParse = gcp.remainder;
|
||||||
|
|
||||||
|
int i = 0;
|
||||||
|
for (; toParse[i] != '\0'; i++) {
|
||||||
|
gameName += toParse[i];
|
||||||
|
}
|
||||||
|
netVersion = (NetProtocolVersion) toParse[i+1];
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
NetQuake::ConnectionRequest::ConnectionRequest(GenericControlPacket gcp)
|
||||||
|
{
|
||||||
|
if (gcp.opcode != CONTROL_SERVER_INFO_REQUEST) {
|
||||||
|
throw std::runtime_error("Wrong packet type given to server info request constructor.");
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string toParse = gcp.remainder;
|
||||||
|
|
||||||
|
int i = 0;
|
||||||
|
for (; toParse[i] != '\0'; i++) {
|
||||||
|
gameName += toParse[i];
|
||||||
|
}
|
||||||
|
netVersion = (NetProtocolVersion)toParse[i + 1];
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
NetQuake::Internal::InternalRequestConsole::InternalRequestConsole()
|
||||||
|
{
|
||||||
|
std::print("New Internal Request Console Created.\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
int NetQuake::Internal::InternalRequestConsole::newEndpointRequest(asio::ip::udp::endpoint ep)
|
||||||
|
{
|
||||||
|
NewEndpointRequestReply nerr;
|
||||||
|
nerr.ready = false;
|
||||||
|
nerr.startRemote = ep;
|
||||||
|
newEndpointRequests.push_back(nerr);
|
||||||
|
return newEndpointRequests.size() - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
NetQuake::Internal::NewEndpointRequestReply NetQuake::Internal::InternalRequestConsole::getEndpointRequest(int id)
|
||||||
|
{
|
||||||
|
return newEndpointRequests[id];
|
||||||
|
}
|
||||||
|
|
||||||
|
void NetQuake::Internal::InternalRequestConsole::Update(Config::Config *config, std::vector<GameEndpoint>* gameEndpointsVector)
|
||||||
|
{
|
||||||
|
|
||||||
|
for (int i = lastEndpointRequestTreated; i < newEndpointRequests.size(); i++) {
|
||||||
|
if (!newEndpointRequests[i].ready) {
|
||||||
|
if(gameEndpointsVector->size() < config->maxPlayers){
|
||||||
|
GameEndpoint* endpoint = new GameEndpoint(config, 1000+i, newEndpointRequests[i].startRemote);
|
||||||
|
newEndpointRequests[i].port = 1000 + i;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
newEndpointRequests[i].error = true;
|
||||||
|
newEndpointRequests[i].reason = "too many players";
|
||||||
|
}
|
||||||
|
|
||||||
|
newEndpointRequests[i].ready = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
NetQuake::Internal::InternalRequestConsole::~InternalRequestConsole()
|
||||||
|
{
|
||||||
|
newEndpointRequests.clear();
|
||||||
|
std::print("Internal Request Console Destroyed.\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
NetQuake::GameEndpoint::GameEndpoint()
|
||||||
|
{
|
||||||
|
throw; // constructor musn't be empty
|
||||||
|
}
|
||||||
|
|
||||||
|
NetQuake::GameEndpoint::GameEndpoint(Config::Config* config, int port, asio::ip::udp::endpoint startRemote)
|
||||||
|
{
|
||||||
|
std::thread internalInternalThread(NetQuake_EndpointServer, config, port, startRemote);
|
||||||
|
internalThread.swap(internalInternalThread);
|
||||||
|
}
|
||||||
|
|
||||||
|
NetQuake::GameEndpoint::~GameEndpoint()
|
||||||
|
{
|
||||||
|
internalThread.~thread();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string NetQuake::ServerMessages::Print::Serialize() {
|
||||||
|
using namespace std::string_literals;
|
||||||
|
std::string retval = text;
|
||||||
|
retval += "\0"s;
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string NetQuake::ServerMessages::ServerInfo::Serialize() {
|
||||||
|
std::string retval = "";
|
||||||
|
using namespace std::string_literals;
|
||||||
|
retval += serverVersion;
|
||||||
|
retval += maxClients;
|
||||||
|
retval += mapName + "\0"s;
|
||||||
|
for (int i = 0; i < precacheModels.size(); i++) {
|
||||||
|
retval += precacheModels[i] + "\0"s;
|
||||||
|
}
|
||||||
|
retval += numModels;
|
||||||
|
for (int i = 0; i < precacheSounds.size(); i++) {
|
||||||
|
retval += precacheSounds[i] + "\0"s;
|
||||||
|
}
|
||||||
|
|
||||||
|
retval += numSounds;
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
std::string NetQuake::ServerMessages::SignOn::Serialize() {
|
||||||
|
std::string retval = "";
|
||||||
|
|
||||||
|
retval += signon;
|
||||||
|
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string NetQuake::ServerMessageBlock::Serialize()
|
||||||
|
{
|
||||||
|
std::string retval;
|
||||||
|
|
||||||
|
using namespace std::string_literals;
|
||||||
|
|
||||||
|
for (int i = 0; i < serverMessages.size(); i++) {
|
||||||
|
retval += serverMessages[i]->type;
|
||||||
|
retval += serverMessages[i]->Serialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
short retvalsize = retval.size() + 4;
|
||||||
|
unsigned char retvalsizechar[2];
|
||||||
|
unsigned char typechar[2];
|
||||||
|
|
||||||
|
retvalsizechar[0] = retvalsize & 0xff;
|
||||||
|
retvalsizechar[1] = (retvalsize >> 8) & 0xff;
|
||||||
|
typechar[1] = this->type & 0xff;
|
||||||
|
typechar[0] = (this->type >> 8) & 0xff;
|
||||||
|
|
||||||
|
std::string fretval = std::string({(char)typechar[0]});
|
||||||
|
fretval += typechar[1];
|
||||||
|
fretval += retvalsizechar[1];
|
||||||
|
fretval += (char)retvalsizechar[0];
|
||||||
|
fretval += retval;
|
||||||
|
return fretval;
|
||||||
|
}
|
259
mpfw_server/NetQuake.h
Normal file
259
mpfw_server/NetQuake.h
Normal file
@ -0,0 +1,259 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
#include "Config.h"
|
||||||
|
#include <asio/ts/internet.hpp>
|
||||||
|
#include <asio.hpp>
|
||||||
|
#include <map>
|
||||||
|
#include <vector>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
|
|
||||||
|
namespace NetQuake {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
struct NetworkGenericPacket { // generic packet straight from server
|
||||||
|
char type[2];
|
||||||
|
char length[2];
|
||||||
|
std::string remainder;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
enum PacketType {
|
||||||
|
NQ_CONTROL_PACKET = 0x8000,
|
||||||
|
NQ_MESSAGE_BLOCK_CHUNK = 0x0001,
|
||||||
|
NQ_MESSAGE_BLOCK_END = 0x0009,
|
||||||
|
NQ_ACK = 0x0002,
|
||||||
|
NQ_UNRELIABLE = 0x0010,
|
||||||
|
NQ_UNKNOWN_PACKET
|
||||||
|
};
|
||||||
|
|
||||||
|
struct SerializedGenericPacket {
|
||||||
|
PacketType type;
|
||||||
|
int length;
|
||||||
|
std::string remainder;
|
||||||
|
};
|
||||||
|
|
||||||
|
SerializedGenericPacket Serialize(NetworkGenericPacket ngp);
|
||||||
|
|
||||||
|
|
||||||
|
enum ClientMessageType {
|
||||||
|
CLIENT_MESSAGE_NOOP = 0x00,
|
||||||
|
CLIENT_MESSAGE_KEEPALIVE = 0x01,
|
||||||
|
CLIENT_MESSAGE_DISCONNECT = 0x02,
|
||||||
|
CLIENT_MESSAGE_MOVEMENT = 0x03,
|
||||||
|
CLIENT_MESSAGE_CONSOLE = 0x04,
|
||||||
|
CLIENT_MESSAGE_UNKNOWN
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ClientMessage {
|
||||||
|
ClientMessageType type;
|
||||||
|
std::string data;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ClientMessageBlock {
|
||||||
|
std::vector<ClientMessage> messages;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct GenericGamePacket {
|
||||||
|
PacketType type;
|
||||||
|
int length;
|
||||||
|
long packetNumber;
|
||||||
|
ClientMessageBlock messageBlock;
|
||||||
|
bool valid = false;
|
||||||
|
};
|
||||||
|
GenericGamePacket toGenericGamePacket(SerializedGenericPacket sgp);
|
||||||
|
|
||||||
|
enum DemoMessageType {
|
||||||
|
DEM_BAD = 0x00,
|
||||||
|
DEM_NOP = 0x01,
|
||||||
|
DEM_DISCONNECT = 0x02,
|
||||||
|
DEM_UPDATESTAT = 0x03,
|
||||||
|
DEM_VERSION = 0x04,
|
||||||
|
DEM_SETVIEW = 0x05,
|
||||||
|
DEM_SOUND = 0x06,
|
||||||
|
DEM_TIME = 0x07,
|
||||||
|
DEM_PRINT = 0x08,
|
||||||
|
DEM_STUFFTEXT = 0x09,
|
||||||
|
DEM_SETANGLE = 0x0A,
|
||||||
|
DEM_SERVERINFO = 0x0B,
|
||||||
|
DEM_LIGHTSTYLE = 0x0C,
|
||||||
|
DEM_UPDATENAME = 0x0D,
|
||||||
|
DEM_UPDATEFRAGS = 0x0E,
|
||||||
|
DEM_CLIENTDATA = 0x0F,
|
||||||
|
DEM_STOPSOUND = 0x10,
|
||||||
|
DEM_UPDATECOLORS = 0x11,
|
||||||
|
DEM_PARTICLE = 0x12,
|
||||||
|
DEM_DAMAGE = 0x13,
|
||||||
|
DEM_SPAWNSTATIC = 0x14,
|
||||||
|
DEM_SPAWNBINARY = 0x15,
|
||||||
|
DEM_SPAWNBASELINE = 0x16,
|
||||||
|
DEM_TEMP_ENTITY = 0x17,
|
||||||
|
DEM_SETPAUSE = 0x18,
|
||||||
|
DEM_SIGNONUM = 0x19,
|
||||||
|
DEM_CENTERPRINT = 0x1A,
|
||||||
|
DEM_KILLEDMONSTER = 0x1B,
|
||||||
|
DEM_FOUNDSECRET = 0x1C,
|
||||||
|
DEM_SPAWNSTATICSOUND = 0x1D,
|
||||||
|
DEM_INTERMISSION = 0x1E,
|
||||||
|
DEM_FINALE = 0x1F,
|
||||||
|
DEM_CDTRACK = 0x20,
|
||||||
|
DEM_SELLSCREEN = 0x21,
|
||||||
|
DEM_CUTSCENE = 0x22,
|
||||||
|
DEM_UPDATEENTITY = 0x80
|
||||||
|
};
|
||||||
|
|
||||||
|
class GenericServerMessage {
|
||||||
|
public:
|
||||||
|
DemoMessageType type;
|
||||||
|
virtual std::string Serialize() = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
namespace ServerMessages {
|
||||||
|
class Print : public GenericServerMessage { // 0x08
|
||||||
|
public:
|
||||||
|
std::string text = "";
|
||||||
|
Print() { this->type = DEM_PRINT; }
|
||||||
|
std::string Serialize();
|
||||||
|
};
|
||||||
|
class ServerInfo : public GenericServerMessage { // 0x0B
|
||||||
|
public:
|
||||||
|
long serverVersion = 15;
|
||||||
|
long maxClients;
|
||||||
|
long multi;
|
||||||
|
std::string mapName;
|
||||||
|
std::vector<std::string> precacheModels;
|
||||||
|
long numModels;
|
||||||
|
std::vector<std::string> precacheSounds;
|
||||||
|
long numSounds;
|
||||||
|
ServerInfo() {
|
||||||
|
this->type = DEM_SERVERINFO;
|
||||||
|
precacheModels = std::vector<std::string>(256);
|
||||||
|
precacheSounds = std::vector<std::string>(256);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
std::string Serialize();
|
||||||
|
};
|
||||||
|
|
||||||
|
class SignOn : public GenericServerMessage {
|
||||||
|
public:
|
||||||
|
long signon;
|
||||||
|
SignOn() { type = DEM_SIGNONUM; }
|
||||||
|
std::string Serialize();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
class ServerMessageBlock{
|
||||||
|
public:
|
||||||
|
PacketType type;
|
||||||
|
int order;
|
||||||
|
std::vector<GenericServerMessage*> serverMessages;
|
||||||
|
std::string Serialize();
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
enum Control_OpCode {
|
||||||
|
CONTROL_CONNECTION_REQUEST = 0x01,
|
||||||
|
CONTROL_SERVER_INFO_REQUEST = 0x02,
|
||||||
|
CONTROL_PLAYER_INFO_REQUEST = 0x03,
|
||||||
|
CONTROL_RULE_INFO_REQUEST = 0x04,
|
||||||
|
CONTROL_SERVER_INFO_REPLY = 0x83
|
||||||
|
};
|
||||||
|
|
||||||
|
struct GenericControlPacket {
|
||||||
|
Control_OpCode opcode;
|
||||||
|
std::string remainder;
|
||||||
|
GenericControlPacket(SerializedGenericPacket sgp);
|
||||||
|
};
|
||||||
|
|
||||||
|
enum NetProtocolVersion {
|
||||||
|
QTEST1 = 0x01,
|
||||||
|
QUNKNOWN = 0x02,
|
||||||
|
QGOLD = 0x03
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ServerInfoRequest {
|
||||||
|
std::string gameName;
|
||||||
|
NetProtocolVersion netVersion;
|
||||||
|
ServerInfoRequest(GenericControlPacket gcp);
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Address {
|
||||||
|
std::string ip;
|
||||||
|
int port;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ServerInfoReply {
|
||||||
|
Address address;
|
||||||
|
std::string hostname;
|
||||||
|
std::string levelname;
|
||||||
|
char currentPlayers;
|
||||||
|
char maxPlayers;
|
||||||
|
NetProtocolVersion netVersion;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::string Serialize(ServerInfoReply sirp);
|
||||||
|
|
||||||
|
struct ConnectionRequest {
|
||||||
|
std::string gameName;
|
||||||
|
NetProtocolVersion netVersion;
|
||||||
|
ConnectionRequest(GenericControlPacket gcp);
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ConnectionRequestReply {
|
||||||
|
bool reject;
|
||||||
|
short port; // LITTLE-ENDIAN (!)(!)(!)(!)(!)
|
||||||
|
char unknown[2]; // [ have fun :) ]
|
||||||
|
|
||||||
|
std::string reason;
|
||||||
|
};
|
||||||
|
|
||||||
|
::std::string Serialize(ConnectionRequestReply crr);
|
||||||
|
|
||||||
|
class GameEndpoint {
|
||||||
|
std::thread internalThread;
|
||||||
|
public:
|
||||||
|
GameEndpoint();
|
||||||
|
GameEndpoint(Config::Config* config, int port, asio::ip::udp::endpoint startRemote);
|
||||||
|
|
||||||
|
~GameEndpoint();
|
||||||
|
};
|
||||||
|
|
||||||
|
namespace Internal {
|
||||||
|
|
||||||
|
struct NewEndpointRequestReply {
|
||||||
|
bool ready = false;
|
||||||
|
bool error = false;
|
||||||
|
std::string reason;
|
||||||
|
int port;
|
||||||
|
asio::ip::udp::endpoint startRemote;
|
||||||
|
};
|
||||||
|
|
||||||
|
class InternalRequestConsole {
|
||||||
|
std::vector<NewEndpointRequestReply> newEndpointRequests;
|
||||||
|
int lastEndpointRequestTreated = 0;
|
||||||
|
public:
|
||||||
|
InternalRequestConsole();
|
||||||
|
|
||||||
|
int newEndpointRequest(asio::ip::udp::endpoint ep);
|
||||||
|
NewEndpointRequestReply getEndpointRequest(int id);
|
||||||
|
void Update(Config::Config *config, std::vector<GameEndpoint>* gameEndpointsVector);
|
||||||
|
~InternalRequestConsole();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
std::string AcknowledgePacket(int packet);
|
||||||
|
|
||||||
|
|
||||||
|
void NetQuake_ControlServer(Internal::InternalRequestConsole* internalRequestConsole, Config::Config* config, bool* serverRunning);
|
||||||
|
void NetQuake_EndpointServer(Config::Config* config, int port, asio::ip::udp::endpoint startRemote);
|
||||||
|
|
||||||
|
|
||||||
|
}
|
85
mpfw_server/main.cpp
Normal file
85
mpfw_server/main.cpp
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
// // MPFW SERVER
|
||||||
|
// //
|
||||||
|
// * * //
|
||||||
|
// * . * // implemented
|
||||||
|
// *I* // |
|
||||||
|
// * // v
|
||||||
|
// // (Net)Quake(World)-compatible game server
|
||||||
|
// // ^
|
||||||
|
// // |
|
||||||
|
// // eventually?
|
||||||
|
// //
|
||||||
|
// //
|
||||||
|
//// github.com/safariminer
|
||||||
|
/// tilde.town/~safariminer
|
||||||
|
//
|
||||||
|
|
||||||
|
//
|
||||||
|
// Notes relative to netquake
|
||||||
|
// - based on : https://www.gamers.org/dEngine/quake/QDP/qnp.html
|
||||||
|
// - Netquake = UDP
|
||||||
|
// - all numbers = big endian unless exception
|
||||||
|
// - don't blindly follow the spec:
|
||||||
|
// | game endpoints in quake don't have auth
|
||||||
|
// | do not use basic range, randomize ports throughout the available range
|
||||||
|
// | verify ip and port
|
||||||
|
// | even though the spec calls for it, don't display IP/port info in playerinfo control
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifdef _DEBUG
|
||||||
|
#define MPFW_SERVER_VERSION std::string(std::string("Debug Build ") + std::string(__DATE__))
|
||||||
|
#else
|
||||||
|
#define MPFW_SERVER_VERSION "Release build"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <iostream> // basic io
|
||||||
|
#include <vector> // dynamic arrays
|
||||||
|
#include <map> // keyval maps
|
||||||
|
#include <asio.hpp>
|
||||||
|
#include <asio/ts/buffer.hpp>
|
||||||
|
#include <asio/ts/internet.hpp>
|
||||||
|
#include <print>
|
||||||
|
|
||||||
|
#include "Config.h"
|
||||||
|
#include "NetQuake.h"
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
|
#include <fstream>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
bool _serverRunning = true;
|
||||||
|
|
||||||
|
|
||||||
|
int rcp = 0;
|
||||||
|
|
||||||
|
Config::Config *config = new Config::Config;
|
||||||
|
|
||||||
|
|
||||||
|
int main(int argc, char** argv) {
|
||||||
|
|
||||||
|
|
||||||
|
std::print("MPFW Server\nBuild Version: {}\n--------------------\n", MPFW_SERVER_VERSION);
|
||||||
|
|
||||||
|
std::vector<NetQuake::GameEndpoint> gameEndpoints;
|
||||||
|
NetQuake::Internal::InternalRequestConsole* internalRequestConsole = new NetQuake::Internal::InternalRequestConsole();
|
||||||
|
|
||||||
|
asio::io_context io_context;
|
||||||
|
|
||||||
|
std::print("Starting control server thread...\n");
|
||||||
|
|
||||||
|
std::thread controlServer(NetQuake::NetQuake_ControlServer, internalRequestConsole, config, &_serverRunning);
|
||||||
|
|
||||||
|
controlServer.detach();
|
||||||
|
std::print("Started control server thread\n");
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
while (_serverRunning) {
|
||||||
|
internalRequestConsole->Update(config, &gameEndpoints);
|
||||||
|
}
|
||||||
|
|
||||||
|
delete internalRequestConsole;
|
||||||
|
return 0;
|
||||||
|
}
|
144
mpfw_server/mpfw_server.vcxproj
Normal file
144
mpfw_server/mpfw_server.vcxproj
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
|
<ItemGroup Label="ProjectConfigurations">
|
||||||
|
<ProjectConfiguration Include="Debug|Win32">
|
||||||
|
<Configuration>Debug</Configuration>
|
||||||
|
<Platform>Win32</Platform>
|
||||||
|
</ProjectConfiguration>
|
||||||
|
<ProjectConfiguration Include="Release|Win32">
|
||||||
|
<Configuration>Release</Configuration>
|
||||||
|
<Platform>Win32</Platform>
|
||||||
|
</ProjectConfiguration>
|
||||||
|
<ProjectConfiguration Include="Debug|x64">
|
||||||
|
<Configuration>Debug</Configuration>
|
||||||
|
<Platform>x64</Platform>
|
||||||
|
</ProjectConfiguration>
|
||||||
|
<ProjectConfiguration Include="Release|x64">
|
||||||
|
<Configuration>Release</Configuration>
|
||||||
|
<Platform>x64</Platform>
|
||||||
|
</ProjectConfiguration>
|
||||||
|
</ItemGroup>
|
||||||
|
<PropertyGroup Label="Globals">
|
||||||
|
<VCProjectVersion>17.0</VCProjectVersion>
|
||||||
|
<Keyword>Win32Proj</Keyword>
|
||||||
|
<ProjectGuid>{67ceac9d-b0f3-4ab3-b3ff-0a9adad98e45}</ProjectGuid>
|
||||||
|
<RootNamespace>mpfwserver</RootNamespace>
|
||||||
|
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
|
||||||
|
</PropertyGroup>
|
||||||
|
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
|
||||||
|
<ConfigurationType>Application</ConfigurationType>
|
||||||
|
<UseDebugLibraries>true</UseDebugLibraries>
|
||||||
|
<PlatformToolset>v143</PlatformToolset>
|
||||||
|
<CharacterSet>Unicode</CharacterSet>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
|
||||||
|
<ConfigurationType>Application</ConfigurationType>
|
||||||
|
<UseDebugLibraries>false</UseDebugLibraries>
|
||||||
|
<PlatformToolset>v143</PlatformToolset>
|
||||||
|
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||||
|
<CharacterSet>Unicode</CharacterSet>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
|
||||||
|
<ConfigurationType>Application</ConfigurationType>
|
||||||
|
<UseDebugLibraries>true</UseDebugLibraries>
|
||||||
|
<PlatformToolset>v143</PlatformToolset>
|
||||||
|
<CharacterSet>Unicode</CharacterSet>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
|
||||||
|
<ConfigurationType>Application</ConfigurationType>
|
||||||
|
<UseDebugLibraries>false</UseDebugLibraries>
|
||||||
|
<PlatformToolset>v143</PlatformToolset>
|
||||||
|
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||||
|
<CharacterSet>Unicode</CharacterSet>
|
||||||
|
</PropertyGroup>
|
||||||
|
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||||
|
<ImportGroup Label="ExtensionSettings">
|
||||||
|
</ImportGroup>
|
||||||
|
<ImportGroup Label="Shared">
|
||||||
|
</ImportGroup>
|
||||||
|
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||||
|
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||||
|
</ImportGroup>
|
||||||
|
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||||
|
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||||
|
</ImportGroup>
|
||||||
|
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||||
|
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||||
|
</ImportGroup>
|
||||||
|
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||||
|
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||||
|
</ImportGroup>
|
||||||
|
<PropertyGroup Label="UserMacros" />
|
||||||
|
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||||
|
<ClCompile>
|
||||||
|
<WarningLevel>Level3</WarningLevel>
|
||||||
|
<SDLCheck>true</SDLCheck>
|
||||||
|
<PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||||
|
<ConformanceMode>true</ConformanceMode>
|
||||||
|
</ClCompile>
|
||||||
|
<Link>
|
||||||
|
<SubSystem>Console</SubSystem>
|
||||||
|
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||||
|
</Link>
|
||||||
|
</ItemDefinitionGroup>
|
||||||
|
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||||
|
<ClCompile>
|
||||||
|
<WarningLevel>Level3</WarningLevel>
|
||||||
|
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||||
|
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||||
|
<SDLCheck>true</SDLCheck>
|
||||||
|
<PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||||
|
<ConformanceMode>true</ConformanceMode>
|
||||||
|
</ClCompile>
|
||||||
|
<Link>
|
||||||
|
<SubSystem>Console</SubSystem>
|
||||||
|
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||||
|
</Link>
|
||||||
|
</ItemDefinitionGroup>
|
||||||
|
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||||
|
<ClCompile>
|
||||||
|
<WarningLevel>Level3</WarningLevel>
|
||||||
|
<SDLCheck>true</SDLCheck>
|
||||||
|
<PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||||
|
<ConformanceMode>true</ConformanceMode>
|
||||||
|
<AdditionalIncludeDirectories>$(SOLUTIONDIR)deps/include</AdditionalIncludeDirectories>
|
||||||
|
<LanguageStandard>stdcpplatest</LanguageStandard>
|
||||||
|
</ClCompile>
|
||||||
|
<Link>
|
||||||
|
<SubSystem>Console</SubSystem>
|
||||||
|
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||||
|
<AdditionalLibraryDirectories>$(SOLUTIONDIR)deps/lib</AdditionalLibraryDirectories>
|
||||||
|
<AdditionalDependencies>winmm.lib;ws2_32.lib;$(CoreLibraryDependencies);%(AdditionalDependencies)</AdditionalDependencies>
|
||||||
|
</Link>
|
||||||
|
</ItemDefinitionGroup>
|
||||||
|
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||||
|
<ClCompile>
|
||||||
|
<WarningLevel>Level3</WarningLevel>
|
||||||
|
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||||
|
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||||
|
<SDLCheck>true</SDLCheck>
|
||||||
|
<PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||||
|
<ConformanceMode>true</ConformanceMode>
|
||||||
|
<AdditionalIncludeDirectories>$(SOLUTIONDIR)deps/include</AdditionalIncludeDirectories>
|
||||||
|
<LanguageStandard>stdcpplatest</LanguageStandard>
|
||||||
|
</ClCompile>
|
||||||
|
<Link>
|
||||||
|
<SubSystem>Console</SubSystem>
|
||||||
|
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||||
|
<AdditionalLibraryDirectories>$(SOLUTIONDIR)deps/lib</AdditionalLibraryDirectories>
|
||||||
|
<AdditionalDependencies>winmm.lib;ws2_32.lib;$(CoreLibraryDependencies);%(AdditionalDependencies)</AdditionalDependencies>
|
||||||
|
</Link>
|
||||||
|
</ItemDefinitionGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<ClCompile Include="main.cpp" />
|
||||||
|
<ClCompile Include="NetQuake.cpp" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<ClInclude Include="Config.h" />
|
||||||
|
<ClInclude Include="NetQuake.h" />
|
||||||
|
</ItemGroup>
|
||||||
|
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||||
|
<ImportGroup Label="ExtensionTargets">
|
||||||
|
</ImportGroup>
|
||||||
|
</Project>
|
33
mpfw_server/mpfw_server.vcxproj.filters
Normal file
33
mpfw_server/mpfw_server.vcxproj.filters
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
|
<ItemGroup>
|
||||||
|
<Filter Include="Source Files">
|
||||||
|
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
|
||||||
|
<Extensions>cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
|
||||||
|
</Filter>
|
||||||
|
<Filter Include="Header Files">
|
||||||
|
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
|
||||||
|
<Extensions>h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd</Extensions>
|
||||||
|
</Filter>
|
||||||
|
<Filter Include="Resource Files">
|
||||||
|
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
|
||||||
|
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
|
||||||
|
</Filter>
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<ClCompile Include="main.cpp">
|
||||||
|
<Filter>Source Files</Filter>
|
||||||
|
</ClCompile>
|
||||||
|
<ClCompile Include="NetQuake.cpp">
|
||||||
|
<Filter>Source Files</Filter>
|
||||||
|
</ClCompile>
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<ClInclude Include="NetQuake.h">
|
||||||
|
<Filter>Header Files</Filter>
|
||||||
|
</ClInclude>
|
||||||
|
<ClInclude Include="Config.h">
|
||||||
|
<Filter>Header Files</Filter>
|
||||||
|
</ClInclude>
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
11
mpfw_server/mpfw_server.vcxproj.user
Normal file
11
mpfw_server/mpfw_server.vcxproj.user
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||||
|
<LocalDebuggerWorkingDirectory>$(SOLUTIONDIR)gameenv/</LocalDebuggerWorkingDirectory>
|
||||||
|
<DebuggerFlavor>WindowsLocalDebugger</DebuggerFlavor>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||||
|
<LocalDebuggerWorkingDirectory>$(SOLUTIONDIR)gameenv/</LocalDebuggerWorkingDirectory>
|
||||||
|
<DebuggerFlavor>WindowsLocalDebugger</DebuggerFlavor>
|
||||||
|
</PropertyGroup>
|
||||||
|
</Project>
|
Loading…
x
Reference in New Issue
Block a user