mirror of
https://github.com/ZDoom/gzdoom.git
synced 2024-11-17 18:01:39 +00:00
304 lines
11 KiB
Text
304 lines
11 KiB
Text
|
ZDoom 1.19 offers a partial conversion of the Doom code to C++. As such, some
|
||
|
functions and classes have been encapsulated in classes. This document tries
|
||
|
to give an overview of those classes and how to use them.
|
||
|
|
||
|
Command line parameters
|
||
|
=======================
|
||
|
|
||
|
Previous versions of ZDoom kept the command line in the myargv and myargc
|
||
|
global variables. This version uses a single variable, Args, of type DArgs.
|
||
|
This class is defined in m_argv.h and implemented in m_argv.cpp. The three
|
||
|
most important functions are:
|
||
|
|
||
|
int DArgs::CheckParm (const char *check) const;
|
||
|
|
||
|
Checks to see if a parameter exists. If it does, it's index number is
|
||
|
returned. Use the GetArg() method to retrieve an arguments specified
|
||
|
after it.
|
||
|
|
||
|
char *DArgs::CheckValue (const char *check) const;
|
||
|
|
||
|
This is similar to CheckParm(), but it returns the value of the argument
|
||
|
immediately following parameter. If the parameter is not present, or it
|
||
|
has no argument, NULL is returned.
|
||
|
|
||
|
char *DArgs::GetArg (int index) const;
|
||
|
|
||
|
Retrieves the value of an argument. This is similar to argv[index].
|
||
|
|
||
|
For the other functions, check the implementation in m_argv.cpp to see what
|
||
|
they do.
|
||
|
|
||
|
Error messages
|
||
|
==============
|
||
|
|
||
|
There are two functions for reporting error conditions: I_Error() and
|
||
|
I_FatalError(). Use the first for errors that can be recovered from. It will
|
||
|
drop the user to the fullscreen console and print the message. Use the
|
||
|
second for error conditions that are much harder to recover from. It will
|
||
|
terminate the program and display the message.
|
||
|
|
||
|
Console commands
|
||
|
================
|
||
|
|
||
|
Adding new console commands is now very simple, compared to previous
|
||
|
versions of ZDoom. Registration is now completely automatic if you use the
|
||
|
BEGIN_COMMAND and END_COMMAND macros defined in c_dispatch.h. A simple
|
||
|
command could be implemented like this:
|
||
|
|
||
|
#include "c_dispatch.h"
|
||
|
|
||
|
BEGIN_COMMAND(hello)
|
||
|
{
|
||
|
Printf (PRINT_HIGH, "Hello, world!\n");
|
||
|
}
|
||
|
END_COMMAND(hello)
|
||
|
|
||
|
The parameter to the BEGIN_COMMAND and END_COMMAND macros is the name of
|
||
|
the command, as typed at the console (in this case, "hello"). You should use
|
||
|
the same name for both BEGIN_ and END_. You can access the arguments to the
|
||
|
command using argc and argv. If you want to know who activated the command,
|
||
|
m_Instigator is a pointer to the actor of the player who called it.
|
||
|
|
||
|
Console variables
|
||
|
=================
|
||
|
|
||
|
Adding new console variables is also similar. The c_cvars.h file contains
|
||
|
four macros related to using cvars:
|
||
|
|
||
|
CVAR (name, default, flags)
|
||
|
|
||
|
Declares a new console variable with the name "name" and default
|
||
|
value "default" and flags "flags". (See c_cvars.h for the different
|
||
|
flags). An example:
|
||
|
|
||
|
CVAR (var_friction, "1", CVAR_SERVERINFO);
|
||
|
|
||
|
EXTERN_CVAR (name)
|
||
|
|
||
|
Imports a cvar defined elsewhere. This just generates the code
|
||
|
"extern cvar_t name;", but you should still use this macro.
|
||
|
|
||
|
BEGIN_CUSTOM_CVAR (name, default, flags)
|
||
|
END_CUSTOM_CVAR (name)
|
||
|
|
||
|
Declares a cvar that performs some special action each time its value
|
||
|
changes. You should use a compound statement between the BEGIN_ and
|
||
|
END_ macros, just like with commands above. Any code between these
|
||
|
two macros can use "var" to access the associated cvar. Example:
|
||
|
|
||
|
BEGIN_CUSTOM_CVAR (splashfactor, "1.0", CVAR_SERVERINFO)
|
||
|
{
|
||
|
if (var.value <= 0.0f)
|
||
|
var.Set (1.0f);
|
||
|
else
|
||
|
selfthrustscale = 1.0f / var.value;
|
||
|
}
|
||
|
END_CUSTOM_CVAR (splashfactor)
|
||
|
|
||
|
This example also illustrates bounds checking for cvars. The basic
|
||
|
idea is:
|
||
|
|
||
|
if (variable is within range)
|
||
|
<do something with it>
|
||
|
else
|
||
|
<set it to something that is in range>
|
||
|
|
||
|
Calling Set() will automatically execute the code block again, so you
|
||
|
must make sure that you do nothing but reset the value of the cvar if
|
||
|
it's out of range. The code block will also be executed at program
|
||
|
startup when the cvar is constructed.
|
||
|
|
||
|
The DObject class hierarchy
|
||
|
===========================
|
||
|
|
||
|
Most classes in ZDoom 1.18 are descendants of the DObject class. This class
|
||
|
provides basic typechecking and serialization functionality. In a future
|
||
|
version of ZDoom, it will also provide a means to interface C++ code with
|
||
|
DoomScript. If you want to be able to perform type-checking on a class, save
|
||
|
it to disk, or access it from DoomScript (in a later version), it should be
|
||
|
a descendant of DObject.
|
||
|
|
||
|
The basic features of DObject require that you use some of the macros in
|
||
|
dobject.h: DECLARE_CLASS, IMPLEMENT_CLASS, DECLARE_SERIAL, and
|
||
|
IMPLEMENT_SERIAL. For a class that won't be saved to disk, do this:
|
||
|
|
||
|
class MyClass : public DObject
|
||
|
{
|
||
|
DECLARE_CLASS (MyClass, DObject)
|
||
|
public:
|
||
|
...
|
||
|
Rest of class definition
|
||
|
...
|
||
|
}
|
||
|
|
||
|
Then someplace else, where you define the class, add this line:
|
||
|
|
||
|
IMPLEMENT_CLASS (MyClass, DObject)
|
||
|
|
||
|
(You should be sure to replace MyClass with the actual name of your class
|
||
|
and DObject with the name of your class's superclass.)
|
||
|
|
||
|
For a class that can be saved to disk (ie, a serializable class), use
|
||
|
DECLARE_SERIAL and IMPLEMENT_SERIAL instead. Serializable classes require
|
||
|
default constructors. If you don't want anybody to be able to create an
|
||
|
instance of the class using the default constructor, you can hide it
|
||
|
inside the private portion of the class definition:
|
||
|
|
||
|
...
|
||
|
private:
|
||
|
MyClass () {}
|
||
|
...
|
||
|
|
||
|
Serializable classes also require that you define a Serialize() method.
|
||
|
This function should call the superclass's Serialize() method and then
|
||
|
either store or extract the fields for the class. (Whenever you change
|
||
|
the contents of a class, be sure to change its accompanying Serialize()
|
||
|
method.) A generic Serialize() looks like this:
|
||
|
|
||
|
void MyClass::Serialize (FArchive &arc)
|
||
|
{
|
||
|
Super::Serialize (arc);
|
||
|
if (arc.IsStoring ())
|
||
|
{
|
||
|
arc << m_field1 << m_field2 << m_field3 << ... ;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
arc >> m_field1 >> m_field2 >> m_field3 >> ... ;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
There are also some structs that use some serialization code that you
|
||
|
should be sure to change if you change the structs. The code for
|
||
|
serializing sector_t and line_t can be found in P_SerializeWorld() in
|
||
|
p_saveg.cpp. The code for serializing player_t is in p_user.cpp as
|
||
|
player_s::Serialize().
|
||
|
|
||
|
To determine the type of an object, you can use the IsA() and IsKindOf()
|
||
|
methods of DObject. IsA() tests if the object is an instance of a
|
||
|
specific type of class, and is probably not what you want. IsKindOf()
|
||
|
checks if an object is a specific type of class or one of its
|
||
|
descendants. Example: Suppose anActor points to an instance of AActor.
|
||
|
(anActor->IsA (RUNTIME_CLASS(DThinker))) will return false because AActor
|
||
|
is not the same as DThinker, but (anActor->IsKindOf
|
||
|
(RUNTIME_CLASS(DThinker))) will return true because AActor is a
|
||
|
descendant of DThinker.
|
||
|
|
||
|
Pointer cleanup
|
||
|
===============
|
||
|
|
||
|
Whenever an object is destroyed, ZDoom will attempt to NULL any pointers that
|
||
|
pointed to that object. This eliminates various anomalies caused by trying to
|
||
|
reference stale pointers. This mechanism is not entirely transparent and
|
||
|
requires some work on your part. When you create a subclass of DObject that
|
||
|
contains pointers, you should use the IMPLEMENT_POINTY_SERIAL or
|
||
|
IMPLEMENT_POINTY_CLASS macros instead of IMPLEMENT_SERIAL or IMPLEMENT_CLASS.
|
||
|
Following the macro, you list each element of the class that points to a
|
||
|
DObject (or a subclass of it). If the pointer points to something that is not
|
||
|
a DObject (such as a char *), you do not need to include it in this list.
|
||
|
After you list all the pointers, use the END_POINTERS macro.
|
||
|
|
||
|
The implementation for the AActor class looks like this:
|
||
|
|
||
|
IMPLEMENT_POINTY_SERIAL (AActor, DThinker)
|
||
|
DECLARE_POINTER (target)
|
||
|
DECLARE_POINTER (lastenemy)
|
||
|
DECLARE_POINTER (tracer)
|
||
|
DECLARE_POINTER (goal)
|
||
|
END_POINTERS
|
||
|
|
||
|
If you add pointers to any class, you need to update this list accordingly.
|
||
|
The player class uses a hack, because it is not derived from DObject, to
|
||
|
clear pointers. See player_s::FixPointers() in p_user.cpp if you add any
|
||
|
pointers to player_s.
|
||
|
|
||
|
When you want to destroy any object derived from DThinker (this includes all
|
||
|
actors), call that object's Destroy() method. Do not use delete, because it
|
||
|
is not guaranteed to perform all necessary cleanup. If the object is not
|
||
|
derived from DThinker, however, you can safely call delete on it.
|
||
|
|
||
|
As an example, if you have an instance of AActor called actor, and you want
|
||
|
to get rid of it, use:
|
||
|
actor->Destroy ();
|
||
|
|
||
|
If you have an instance of DCanvas called canvas and want to get rid of it,
|
||
|
either of the following will work (but not both):
|
||
|
canvas->Destroy (); or
|
||
|
delete canvas;
|
||
|
|
||
|
Thinkers
|
||
|
========
|
||
|
|
||
|
Almost everything that can affect the appearance of the world is implemented
|
||
|
as a subclass of DThinker. The important base classes are:
|
||
|
|
||
|
DPolyAction
|
||
|
|
||
|
A polyobject that is doing something (DRotatePoly, DMovePoly, or
|
||
|
DPolyDoor).
|
||
|
|
||
|
DSectorEffect
|
||
|
|
||
|
An effect that occurs inside a certain sector. This is further broken
|
||
|
up into DLighting (for lighting changes) and DMover (for floor/
|
||
|
ceiling movement). DMover is also subclassed into DMovingFloor (for
|
||
|
moving floors only) and DMovingCeiling (for moving ceilings only).
|
||
|
For effects that move both the floor and ceiling, subclass DMover
|
||
|
and have a look at either DPillar or DElevator.
|
||
|
|
||
|
AActor
|
||
|
|
||
|
This is probably the most important. All players, monsters, items,
|
||
|
ammo, weapons, and anything else that appears as a sprite in the
|
||
|
world is an instance of AActor. This is nowhere near as encapsulated
|
||
|
as I would like it to be because of the necessity to maintain
|
||
|
compatibility with DeHackEd patches until DoomScript is done. This
|
||
|
means that there is no easy way to subclass AActor and spawn an
|
||
|
instance of the subclass in the map. I don't like it, but it's a step
|
||
|
in the right direction.
|
||
|
|
||
|
To find all instances of a specific type of thinker in a map, you can use the
|
||
|
TThinkerIterator template class. It's usage is simple:
|
||
|
|
||
|
TThinkerIterator<TypeOfClass> iterator;
|
||
|
TypeOfClass *object;
|
||
|
|
||
|
while ( (object = iterator.Next ()) )
|
||
|
{
|
||
|
// do something with the object
|
||
|
}
|
||
|
|
||
|
AActor methods
|
||
|
==============
|
||
|
|
||
|
A few of the functions that operate on actors have been moved inside the
|
||
|
AActor class:
|
||
|
|
||
|
P_ClearTidHashes() -> AActor::ClearTIDHashes()
|
||
|
P_AddMobjToHash() -> AActor::AddToHash()
|
||
|
P_RemoveMobjFromHash() -> AActor::RemoveFromHash()
|
||
|
P_FindMobjByTid() -> AActor::FindByTID()
|
||
|
P_FindGoal() -> AActor::FindGoal()
|
||
|
|
||
|
P_UnsetThingPosition() -> AActor::UnlinkFromWorld()
|
||
|
P_SetThingPosition() -> AActor::LinkToWorld()
|
||
|
|
||
|
AActor::SetOrigin() has also been introduced. It simply moves the actor to
|
||
|
a specific location on the map without performing any collision detection.
|
||
|
|
||
|
A bit more on serialization
|
||
|
===========================
|
||
|
|
||
|
The serialization mechanism is a fairly standard, yet powerful, framework
|
||
|
that can save a bunch of objects to disk and properly maintain pointers
|
||
|
between them. When you store a pointer to an object, the object will also be
|
||
|
stored to disk if it hasn't been already. When you read the pointer, the
|
||
|
object it points to will also be read from disk if it hasn't been already.
|
||
|
This allows pointers to objects to be preserved without any extra effort on
|
||
|
your part. Pointers to something other than a DObject or one of its
|
||
|
descendents need to be implemented specially by overloading operator << (see
|
||
|
info.h for a few of these). If you're not sure how serialization works, take
|
||
|
a look at some of the serialization functions for the existing classes by
|
||
|
grepping through the source for "::Serialize" (without the quotes).
|