[v0.9.0] ZetaBot: The ZScript Bot

Projects that alter game functions but do not include new maps belong here.
Forum rules
The Projects forums are ONLY for YOUR PROJECTS! If you are asking questions about a project, either find that project's thread, or start a thread in the General section instead.

Got a cool project idea but nothing else? Put it in the project ideas thread instead!

Projects for any Doom-based engine (especially 3DGE) are perfectly acceptable here too.

Please read the full rules for more details.

Re: [v0.8.1] ZetaBot: The ZScript Bot

Postby Gustavo6046 » Thu Aug 29, 2019 11:30 am

a) What I mean is where in the map I'd place these nodes. I'm talking about X and Y coordinates.
b) The nodelist format is condensed to minimize it, but basically it's a sequence, split into map segments, and each map segment contains a list of nodes:

Code: Select allExpand view
MAP01::{nodes};;MAP02::{nodes};; . . .


where {nodes} is a colon-separated list of nodes.

Code: Select allExpand view
node1:node2:node3:...


By convention, ::NONE is an empty nodelist definition.

Each node definition is a comma-separated list of values, where each value has a specific meaning based on its position:

Code: Select allExpand view
nx,ny,nz,nt,ud


where

  • nx, ny, nz -> coordinates for the node's initial position;
  • nt -> a value in the NavigationType enum (shown earlier), to define certain nodes as jump nodes, avoid nodes, etc.;
  • ud -> the angle of the node (ud stands for use direction).


-----------------------


Also, in other news, I'm going to write a new pathfinder for ZetaBot! Instead of pathfinding from the bot to the node, it'll start at the node and find its way back to the bot (reverse A* pathfinding). This makes it possible to branch the pathfinding.

For example, let's say we encounter a locked door node (future plans!). In this case, we keep the pathfinding branch for that door, as it tries to find another way around that door, while another branch is created, going through as if the door were unlocked; this branch will look for the key to that door, instead of the bot, and once it finds the key, it will resume looking for the bot as usual. This Stack Exchange post contains a more detailed example case and will definitely explain this better than my pitiable writing can do.
User avatar
Gustavo6046
 
Joined: 13 May 2017
Location: In an urban area in Brazil.
Discord: Gustavo6046#9009

Re: [v0.8.1] ZetaBot: The ZScript Bot

Postby Gustavo6046 » Thu Aug 29, 2019 5:48 pm

Lately I've been trying to test a 0.8.2-rc1, but it freezes after a while of playing with it, and I don't know why that happens. Is there an easy way of debugging that? It's GZDoom g4.2.0, and I compiled it myself using gcc. Maybe it's not the ZetaBot's fault...
User avatar
Gustavo6046
 
Joined: 13 May 2017
Location: In an urban area in Brazil.
Discord: Gustavo6046#9009

Re: [v0.8.1] ZetaBot: The ZScript Bot

Postby _mental_ » Thu Aug 29, 2019 11:22 pm

It’s very subjective how easy or hard some way is.

If you can reproduce the freeze reliably, run GZDoom under GDB. When it freezes, switch to GDB, press Ctrl+C, do bt command. Examining callstack usually reveals enough info to figure out the problem.
As it most likely an infinite loop in a script, running with +vm_jit 0 may help to get proper callstack.
_mental_
 
 
 
Joined: 07 Aug 2011

Re: [v0.8.1] ZetaBot: The ZScript Bot

Postby Gustavo6046 » Fri Aug 30, 2019 11:00 am

It's a function call, but I don't know which, and I can't see which using backtrace because I don't have the debugging symbols, which I can't have, because building GZDoom with -DCMAKE_BUILD_TYPE=Debug makes this error:

Code: Select allExpand view
gzdoom: /home/gustavo6046/Projects/gzdoom/src/scripting/backend/codegen.cpp:2321: virtual ExpEmit FxPostIncrDecr::Emit(VMFunctionBuilder *): Assertion `ValueType == Base->ValueType && IsNumeric()' failed.


Anyway, this is the stack backtrace:
Code: Select allExpand view
(gdb) backtrace
#0  0x00007ffff74f32d2 in ?? () from /lib/x86_64-linux-gnu/libc.so.6
#1  0x0000555555fc0f53 in ?? ()
#2  0x0000555555fead7a in VMExec_Unchecked::ExecScriptFunc(VMFrameStack*, VMReturn*, int) ()
#3  0x0000555555feb6c0 in VMExec_Unchecked::Exec(VMFunction*, VMValue*, int, VMReturn*, int) ()
#4  0x0000555555fe9671 in VMExec_Unchecked::ExecScriptFunc(VMFrameStack*, VMReturn*, int) ()
#5  0x0000555555feb6c0 in VMExec_Unchecked::Exec(VMFunction*, VMValue*, int, VMReturn*, int) ()
#6  0x0000555555fe9671 in VMExec_Unchecked::ExecScriptFunc(VMFrameStack*, VMReturn*, int) ()
#7  0x0000555555feb6c0 in VMExec_Unchecked::Exec(VMFunction*, VMValue*, int, VMReturn*, int) ()
#8  0x0000555555fe9671 in VMExec_Unchecked::ExecScriptFunc(VMFrameStack*, VMReturn*, int) ()
#9  0x0000555555feb6c0 in VMExec_Unchecked::Exec(VMFunction*, VMValue*, int, VMReturn*, int) ()
#10 0x0000555555fec669 in VMCall(VMFunction*, VMValue*, int, VMReturn*, int) ()
#11 0x0000555555cecf5e in FState::CallAction(AActor*, AActor*, FStateParamInfo*, FState**) ()
#12 0x0000555555c396bc in AActor::SetState(FState*, bool) ()
#13 0x0000555555c49524 in AActor::Tick() ()
#14 0x0000555555d1ea8a in ?? ()
#15 0x0000555555fec5ac in VMCall(VMFunction*, VMValue*, int, VMReturn*, int) ()
#16 0x0000555555d20d14 in DThinker::CallTick() ()
#17 0x0000555555d24fe6 in FThinkerCollection::RunThinkers(FLevelLocals*) ()
#18 0x0000555555c61ef0 in P_Ticker() ()
#19 0x0000555555ba9ad5 in G_Ticker() ()
#20 0x0000555555b8334c in TryRunTics() ()
#21 0x0000555555b78427 in D_DoomLoop() ()
#22 0x0000555555b7b394 in D_DoomMain() ()
#23 0x00005555557b17b4 in main ()



EDIT: I managed to use CMAKE_BUILD_TYPE=RelWithDebInfo to compile a Release binary with symbols (among other debug information). I am still, however, unable to determine which function causes this, as the arguments are optimized out. Fortunately, we have more info:

Code: Select allExpand view
(gdb) bt
#0  0x00007ffff74692d2 in ?? () from /lib/x86_64-linux-gnu/libc.so.6
#1  0x0000555555e2f58b in TArray<DObject*, DObject*>::Insert (this=<optimized out>, index=<optimized out>, item=<optimized out>) at /home/gustavo6046/Projects/gzdoom/src/utility/tarray.h:397
#2  ObjArrayInsert (self=0x555560a189b8, index=0, obj=<optimized out>) at /home/gustavo6046/Projects/gzdoom/src/scripting/backend/dynarrays.cpp:876
#3  0x0000555555e332e7 in AF_FDynArray_Obj_Insert (param=0x55555d95c750, numparam=<optimized out>, ret=<optimized out>, numret=<optimized out>)
    at /home/gustavo6046/Projects/gzdoom/src/scripting/backend/dynarrays.cpp:884
#4  0x0000555555e48dfa in VMExec_Unchecked::ExecScriptFunc (stack=<optimized out>, ret=0x7fffffffc3a0, numret=0) at /home/gustavo6046/Projects/gzdoom/src/scripting/vm/vmexec.h:700
#5  0x0000555555e47b7e in VMExec_Unchecked::Exec (func=<optimized out>, params=0x55555d95c650, numparams=<optimized out>, ret=0x7fffffffc3a0, numret=0)
    at /home/gustavo6046/Projects/gzdoom/src/scripting/vm/vmexec.h:2007
#6  0x0000555555e48d76 in VMExec_Unchecked::ExecScriptFunc (stack=<optimized out>, ret=0x7fffffffc560, numret=1) at /home/gustavo6046/Projects/gzdoom/src/scripting/vm/vmexec.h:714
#7  0x0000555555e47b7e in VMExec_Unchecked::Exec (func=<optimized out>, params=0x55555d95c5b0, numparams=<optimized out>, ret=0x7fffffffc560, numret=1)
    at /home/gustavo6046/Projects/gzdoom/src/scripting/vm/vmexec.h:2007
#8  0x0000555555e48d76 in VMExec_Unchecked::ExecScriptFunc (stack=<optimized out>, ret=0x7fffffffc720, numret=0) at /home/gustavo6046/Projects/gzdoom/src/scripting/vm/vmexec.h:714
#9  0x0000555555e47b7e in VMExec_Unchecked::Exec (func=<optimized out>, params=0x55555d95c510, numparams=<optimized out>, ret=0x7fffffffc720, numret=0)
    at /home/gustavo6046/Projects/gzdoom/src/scripting/vm/vmexec.h:2007
#10 0x0000555555e48d76 in VMExec_Unchecked::ExecScriptFunc (stack=<optimized out>, ret=0x7fffffffc8e8, numret=0) at /home/gustavo6046/Projects/gzdoom/src/scripting/vm/vmexec.h:714
#11 0x0000555555e47b7e in VMExec_Unchecked::Exec (func=<optimized out>, params=0x7fffffffc8b0, numparams=<optimized out>, ret=0x7fffffffc8e8, numret=0)
    at /home/gustavo6046/Projects/gzdoom/src/scripting/vm/vmexec.h:2007
#12 0x0000555555e56375 in VMCall (func=0x55555c6205f0, params=0x7fffffffc8b0, numparams=1, results=0x7fffffffc8e8, numresults=0)
    at /home/gustavo6046/Projects/gzdoom/src/scripting/vm/vmframe.cpp:569
#13 0x0000555555c9f921 in VMCallAction (func=0x55555c6205f0, params=0x55556084a360, numparams=<optimized out>, results=0x0, numresults=8)
    at /home/gustavo6046/Projects/gzdoom/src/scripting/vm/vm.h:485
#14 FState::CallAction (this=0x55555c536258, self=0x55556084a360, stateowner=0x55556084a360, info=0x7fffffffc930, stateret=0x0) at /home/gustavo6046/Projects/gzdoom/src/gamedata/info.cpp:213
#15 0x0000555555c254e4 in AActor::SetState (this=0x55556084a360, newstate=0x55555c536258, nofunction=false) at /home/gustavo6046/Projects/gzdoom/src/playsim/p_mobj.cpp:562
#16 0x0000555555c2c6c4 in AActor::Tick (this=0x55556084a360) at /home/gustavo6046/Projects/gzdoom/src/playsim/p_mobj.cpp:4043
#17 0x0000555555cc69a1 in AF_DThinker_Tick (param=0x7fffffffca70, numparam=<optimized out>, ret=<optimized out>, numret=<optimized out>)
    at /home/gustavo6046/Projects/gzdoom/src/playsim/dthinker.cpp:842
#18 0x0000555555cc566d in DThinker::CallTick (this=<optimized out>) at /home/gustavo6046/Projects/gzdoom/src/playsim/dthinker.cpp:852
#19 FThinkerList::TickThinkers (this=<optimized out>, dest=<optimized out>) at /home/gustavo6046/Projects/gzdoom/src/playsim/dthinker.cpp:575
#20 0x0000555555cc4c5b in FThinkerCollection::RunThinkers (this=0x555556ba95e0 <level+5128>, Level=0x555556ba81d8 <level>) at /home/gustavo6046/Projects/gzdoom/src/playsim/dthinker.cpp:114
#21 0x0000555555c4789d in P_Ticker () at /home/gustavo6046/Projects/gzdoom/src/p_tick.cpp:154
#22 0x0000555555ba52ba in G_Ticker () at /home/gustavo6046/Projects/gzdoom/src/g_game.cpp:1192
#23 0x0000555555b96edc in TryRunTics () at /home/gustavo6046/Projects/gzdoom/src/d_net.cpp:1984
#24 0x0000555555b8e015 in D_DoomLoop () at /home/gustavo6046/Projects/gzdoom/src/d_main.cpp:1032
#25 0x0000555555b913ff in D_DoomMain () at /home/gustavo6046/Projects/gzdoom/src/d_main.cpp:2722
#26 0x00005555557d874c in main (argc=13, argv=0x7fffffffdf38) at /home/gustavo6046/Projects/gzdoom/src/posix/sdl/i_main.cpp:226


EDIT 2: I added a temporary print to my copy of the GZDoom source code, and I managed to fix the error. After undoing this print line, I am still working on 0.8.2. Stay tuned. :)
User avatar
Gustavo6046
 
Joined: 13 May 2017
Location: In an urban area in Brazil.
Discord: Gustavo6046#9009

Re: [v0.8.1] ZetaBot: The ZScript Bot

Postby TDRR » Fri Aug 30, 2019 10:46 pm

Gustavo6046 wrote:a) What I mean is where in the map I'd place these nodes. I'm talking about X and Y coordinates.

Ah, now i understand.

Gustavo6046 wrote:b) The nodelist format is condensed to minimize it, but basically it's a sequence, split into map segments, and each map segment contains a list of nodes:

Code: Select allExpand view
MAP01::{nodes};;MAP02::{nodes};; . . .


where {nodes} is a colon-separated list of nodes.

Code: Select allExpand view
node1:node2:node3:...


By convention, ::NONE is an empty nodelist definition.

Each node definition is a comma-separated list of values, where each value has a specific meaning based on its position:

Code: Select allExpand view
nx,ny,nz,nt,ud


where

  • nx, ny, nz -> coordinates for the node's initial position;
  • nt -> a value in the NavigationType enum (shown earlier), to define certain nodes as jump nodes, avoid nodes, etc.;
  • ud -> the angle of the node (ud stands for use direction).


Awesome! It's not too different from the TDBots nodelist format so it should be very easy to implement. There would be a few issues though, such as:
-Node Studio currently only supports regular path nodes (It's going to support jump nodes a bit later on)
-Only one map per .nod file. I could implement adding to one nodelist but this would be very weird and clunky both on the coding side and on the user side.
And even if i did add such feature let's not forget that ZDoom already imposes a length limit on CVARs saved to .ini files, so it could very much be possible that the same is true for CVARs in RAM.

But on the flip-side:
+Easy point and click controls
+Export nodes for both the TDBots and ZetaBots easily on the same session without having to make them twice
+Having all your nodes in various .nod files instead of having to constantly overwrite the same map nodes if you want to make nodes for a different mapset

Gustavo6046 wrote:Also, in other news, I'm going to write a new pathfinder for ZetaBot! Instead of pathfinding from the bot to the node, it'll start at the node and find its way back to the bot (reverse A* pathfinding). This makes it possible to branch the pathfinding.

For example, let's say we encounter a locked door node (future plans!). In this case, we keep the pathfinding branch for that door, as it tries to find another way around that door, while another branch is created, going through as if the door were unlocked; this branch will look for the key to that door, instead of the bot, and once it finds the key, it will resume looking for the bot as usual. This Stack Exchange post contains a more detailed example case and will definitely explain this better than my pitiable writing can do.


That's really cool! Hopefully this will make the ZetaBots be able to beat levels easily, as much as the AutoDoom bots can, which would be really cool.

Also, i would consider redoing the weapon system, since it's quite hard to do anything more complex than just Doom/Raven/Strife weapons.
Perhaps a "virtual" weapon?
Using the same definition style as regular weapons, which could be done with a class from scratch with all the same codepointers except:
1. The actor is just a regular world actor, but invisible and untouchable, and it's master is the ZetaPawn that spawned it.
2. The codeptrs are sent over to their master (their ZetaPawn), but i'm not a ZScript expert so i don't know how that can be done with parameters. Maybe sending the parameters into a series of actor/user variables and having the actions use them as parameters? That would be the easiest way.
3. If you can't reuse codeptr names then replace the A_ with ZB_. Still much easier than having to make your weapons all over again in a different format.

Even though this method wouldn't account for overlays and gun flashes (Often used to execute a different line of actions along the current weapon layer), neither does the current method anyways. If you are going to do it, i recommend doing it soon because if anyone finally decides to make their mod compatible with the ZetaBots then you may end up having to not do it or potentially lose the support for that mod.

This way, modders wouldn't have to learn a whole new "module" format to make their mods compatible, and they would get more power than with the current way of doing it.
User avatar
TDRR
iDeas from the deep (pit of hacks)
 
Joined: 11 Mar 2018
Location: Venezuela
Operating System: Windows Vista/7 64-bit
Graphics Processor: Intel (Modern GZDoom)

Re: [v0.8.1] ZetaBot: The ZScript Bot

Postby Gustavo6046 » Sat Aug 31, 2019 1:53 pm

I'm pretty sure it's possible to override "code pointers" as functions in Actor subclasses.
User avatar
Gustavo6046
 
Joined: 13 May 2017
Location: In an urban area in Brazil.
Discord: Gustavo6046#9009

Re: [v0.8.1] ZetaBot: The ZScript Bot

Postby Gustavo6046 » Sun Sep 01, 2019 10:30 am

Work has begun on ZetaPlop (GTK) and libzplop. Fasten your seatbelts and hold tight; this awesome flight is yet taking off!

EDIT: I figured it's simpler to write an Eureka plugin. It doesn't have a plugin system, so I'll fork Eureka and make that first.

EDIT 2: This plugin system will use Wren. RIP libzplop. Oh well, I guess ZetaPlop will be written in Wren!
User avatar
Gustavo6046
 
Joined: 13 May 2017
Location: In an urban area in Brazil.
Discord: Gustavo6046#9009

Re: [v0.8.1] ZetaBot: The ZScript Bot

Postby TDRR » Mon Sep 02, 2019 10:39 pm

Now that you are working on a node editor, this post by ketmar may be useful to you. Would be nice to use it to generate simple nodes, and if desired the editor can make more nodes using the auto-generated nodes as a base.
User avatar
TDRR
iDeas from the deep (pit of hacks)
 
Joined: 11 Mar 2018
Location: Venezuela
Operating System: Windows Vista/7 64-bit
Graphics Processor: Intel (Modern GZDoom)

Re: [v0.8.1] ZetaBot: The ZScript Bot

Postby Gustavo6046 » Tue Sep 03, 2019 10:43 am

I would like to store the ZBNODES as a separate lump among the lumps that define a Doom format (or also Hexen format?) map. I wonder if I can access such a lump from ZScript, knowing the current level (can we have the current level's marker lump position from ZScript? That would make searching for the actual lump easier!).

This way, we might not even need CVars at all! Just keep a reference to a ZBNODES lump. If none exists, fallback to using the CVar. We can abstract this by writing a simple, extensible nodelist storage API.

--- EDIT:
TDRR wrote:Now that you are working on a node editor, this post by ketmar may be useful to you. Would be nice to use it to generate simple nodes, and if desired the editor can make more nodes using the auto-generated nodes as a base.


Thank you! It seems I will use subsectors to plop these path nodes ^^

--- EDIT 2:
This Eureka fork, still unnamed.
User avatar
Gustavo6046
 
Joined: 13 May 2017
Location: In an urban area in Brazil.
Discord: Gustavo6046#9009

Re: [v0.8.1] ZetaBot: The ZScript Bot

Postby TDRR » Tue Sep 03, 2019 5:59 pm

Gustavo6046 wrote:I would like to store the ZBNODES as a separate lump among the lumps that define a Doom format (or also Hexen format?) map. I wonder if I can access such a lump from ZScript, knowing the current level (can we have the current level's marker lump position from ZScript? That would make searching for the actual lump easier!).

This way, we might not even need CVars at all! Just keep a reference to a ZBNODES lump. If none exists, fallback to using the CVar. We can abstract this by writing a simple, extensible nodelist storage API.


I don't think such thing is needed, and IMO is just unnecessary complication. You could use a slightly modified version of your current CVAR list to detect line breaks instead of :: and ,
And place multiple maps in that just like you have been doing with the CVAR nodelist. Exporting it along with the map lumps is harder to use both in the user and coding side, and possibly less efficient.

Gustavo6046 wrote:Thank you! It seems I will use subsectors to plop these path nodes ^^

No problem. Hang around on that thread a bit more, since Ketmar does have some more cool stuff about bots which you might want to ask him about.

Gustavo6046 wrote:This Eureka fork, still unnamed.

That's pretty neat. You could handle the nodes just like things, but the only difference is when saving. As in, when you save, the nodes obviously won't be stored into the THINGS/TEXTMAP lump, but on a separate ZBNODES lump. But i haven't messed too much around with Eureka so not sure.

Also, i have a few questions about a waypoint system in ACS. Pretty much just that i don't know how to easily store and check for already stepped nodes. If you want i can send you a runnable .pk3 in a PM.
User avatar
TDRR
iDeas from the deep (pit of hacks)
 
Joined: 11 Mar 2018
Location: Venezuela
Operating System: Windows Vista/7 64-bit
Graphics Processor: Intel (Modern GZDoom)

Re: [v0.8.1] ZetaBot: The ZScript Bot

Postby Gustavo6046 » Wed Sep 04, 2019 3:17 pm

I'm pretty sure we can iterate lumps in order, from ZScript. Thing is, PK3 (DEFLATE family) is not ordered.
User avatar
Gustavo6046
 
Joined: 13 May 2017
Location: In an urban area in Brazil.
Discord: Gustavo6046#9009

Re: [v0.8.1] ZetaBot: The ZScript Bot

Postby Gustavo6046 » Sat Sep 07, 2019 4:00 pm

Almost everything is in place! ZetaBot 0.8.2 is about to be released, now able to read ZBNODES lumps (when right under the lumps of an associated map, excluding GLNODES)! And my Eureka fork seems about fixed!

EDIT 1 ---
Zeureka (as I decided to name the Eureka fork) seems to work pretty well! It does have a fatal error flaw, but I haven't really found anything else right now. It does need more testing, though! It's substantially unstable and I'm just releasing it as an alpha version.

Unfortunately I haven't compiled Zeureka for Windows... not yet.
User avatar
Gustavo6046
 
Joined: 13 May 2017
Location: In an urban area in Brazil.
Discord: Gustavo6046#9009

Re: [v0.8.2] ZetaBot: The ZScript Bot

Postby tihjawi » Tue Sep 10, 2019 3:51 am

Heya.

I don’t understand how to correct play with bot deathmatch mode?

After I type command "deathmatch 1" in GZDoom, I start new DM-game and add bot, but if bot kills me, i won't respawn and game just restart.

And another question: how can I change bot states (Wandering, Following, Attacking, Hunting and Fleeing), coz my bots very stupid and just run to the wall with "wandering" state.

And one more thing: where I can look all of console commands of this bot?
tihjawi
 
Joined: 04 Apr 2018

Re: [v0.8.2] ZetaBot: The ZScript Bot

Postby Gustavo6046 » Tue Sep 10, 2019 8:17 am

tihjawi wrote:After I type command "deathmatch 1" in GZDoom, I start new DM-game and add bot, but if bot kills me, i won't respawn and game just restart.


Unfortunately, those bots don't set the game to a multiplayer-like state, so you might want to enable respawning manually, using sv_forcerespawnsv_singleplayerrespawn

And another question: how can I change bot states (Wandering, Following, Attacking, Hunting and Fleeing), coz my bots very stupid and just run to the wall with "wandering" state.


Those can usually only be set by the bot itself, as it's an internal state thing (it won't hunt an enemy if it doesn't know which to hunt! It always sets its target before going to Hunting or Attacking mode.)

It is planned to eventually make it possible to give orders to bots, a la Unreal Tournament. I already have a class that prints info about what the player is looking at (whether it is a bot or, in zb_debug 2 mode, a pathnode). It will be easy to implement a simple order system from that.

As for the running into walls thing, it's a known bug. A fix is also in the works.

And one more thing: where I can look all of console commands of this bot?


There aren't many currently, but inside the PK3 (which can be opened with any ZIP viewer) there is a KEYCONF lump.

  • Binding any key to +superzeta will spawn a lot of bots when that key is held! This is most effective when zb_respawn is set to True, since otherwise these bots are usually telefragged. I'm working on a way to make newly-spawned bots immune to telefrags.
  • The ztpaths command will show which paths bots can currently traverse. Since paths are dynamic, those can change, even if the nodes themselves didn't change! This includes changing floor heights (doors, lifts, building stairways, etc.), maybe blocking things, stuff like that.
User avatar
Gustavo6046
 
Joined: 13 May 2017
Location: In an urban area in Brazil.
Discord: Gustavo6046#9009

Re: [v0.8.2] ZetaBot: The ZScript Bot

Postby Gustavo6046 » Mon Sep 16, 2019 1:33 pm

0.9.0 has partial order support (currently only other bots can order bots to do things), spawn telefrag immunity, and toggleable idle bot chatter! It is going to come today!
User avatar
Gustavo6046
 
Joined: 13 May 2017
Location: In an urban area in Brazil.
Discord: Gustavo6046#9009

Previous

Return to Gameplay Mods

Who is online

Users browsing this forum: DoomKrakken, Google [Bot] and 15 guests