diff --git a/src/client/entities.qc b/src/client/entities.qc index 8baa6c7c..76dc057f 100644 --- a/src/client/entities.qc +++ b/src/client/entities.qc @@ -82,7 +82,7 @@ Entity_EntityUpdate(float type, float new) Decal_Parse(); break; case ENT_AMBIENTSOUND: - Sound_ParseLoopingEntity(self, new); + ambient_generic_ReadEntity(new); break; case ENT_OLDCAMERA: trigger_camera tc = (trigger_camera)self; diff --git a/src/client/include.src b/src/client/include.src index 0512a1d0..e2e9dcff 100644 --- a/src/client/include.src +++ b/src/client/include.src @@ -3,7 +3,6 @@ fog.qc font.qc sky.qc music.qc -sound.qc sentences.qc prints.qc voice.qc diff --git a/src/client/modelevent.qc b/src/client/modelevent.qc index a0692fd5..ed519141 100644 --- a/src/client/modelevent.qc +++ b/src/client/modelevent.qc @@ -28,7 +28,20 @@ Event_ProcessModel(float flTimeStamp, int iCode, string strData) { switch(iCode) { case 1004: - sound(self, CHAN_BODY, strData, 1.0f, ATTN_NORM); + if (substring(strData, 0, 1) == "*") + sound(self, CHAN_BODY, substring(strData, 1, -1), 1.0f, ATTN_NORM); + else + sound(self, CHAN_BODY, strData, 1.0f, ATTN_NORM); + break; + case 1005: + NSTalkMonster targ = (NSTalkMonster)self; + targ.Sentence(strData); + break; + case 1008: + if (substring(strData, 0, 1) == "*") + sound(self, CHAN_VOICE, substring(strData, 1, -1), 1.0f, ATTN_NORM); + else + sound(self, CHAN_VOICE, strData, 1.0f, ATTN_NORM); break; case 5004: /* view model sound */ localsound(strData, CHAN_AUTO, 1.0); @@ -53,6 +66,8 @@ Event_ProcessModel(float flTimeStamp, int iCode, string strData) pSeat->m_eMuzzleflash.scale = 0.25; pSeat->m_eMuzzleflash.skin = pSeat->m_iVMBones + 3; break; + default: + print(sprintf("Unknown model event: %f %i %S\n", flTimeStamp, iCode, strData)); } } diff --git a/src/client/sound.qc b/src/client/sound.qc deleted file mode 100644 index 53d30873..00000000 --- a/src/client/sound.qc +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright (c) 2016-2020 Marco Cawthorne - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER - * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING - * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -class CCSAmbientSound { - float m_flVolume; - float m_flAttn; - float m_flPitch; - string m_strSample; -}; - -void -Sound_ParseLoopingEntity(entity sndent, float isNew) -{ - float flFlags; - CCSAmbientSound new = (CCSAmbientSound)sndent; - - if (isNew) { - spawnfunc_CCSAmbientSound(); - } - - flFlags = readfloat(); - - if (flFlags & 1) { - new.origin[0] = readcoord(); - new.origin[1] = readcoord(); - new.origin[2] = readcoord(); - setorigin(new, new.origin); - new.m_flVolume = readfloat(); - new.m_flAttn = readbyte(); - new.m_flPitch = readfloat(); - } - - if (flFlags & 2) { - new.m_strSample = readstring(); - } - sound(new, CHAN_VOICE, new.m_strSample, new.m_flVolume, new.m_flAttn, new.m_flPitch); -} diff --git a/src/gs-entbase/server.src b/src/gs-entbase/server.src index ae974342..29a0a059 100644 --- a/src/gs-entbase/server.src +++ b/src/gs-entbase/server.src @@ -11,7 +11,6 @@ server/info_null.qc server/info_notnull.qc server/info_intermission.qc server/button_target.qc -server/ambient_generic.qc server/cycler.qc server/cycler_sprite.qc server/env_beam.qc diff --git a/src/gs-entbase/shared.src b/src/gs-entbase/shared.src index 812be5cf..2f5c7eed 100644 --- a/src/gs-entbase/shared.src +++ b/src/gs-entbase/shared.src @@ -15,6 +15,7 @@ shared/NSVehicle.qc shared/NSMonster.qc shared/NSTalkMonster.qc shared/NSProjectile.qc +shared/ambient_generic.qc shared/decals.qc shared/spraylogo.qc shared/func_friction.qc diff --git a/src/gs-entbase/shared/NSVehicle.h b/src/gs-entbase/shared/NSVehicle.h index 53cfee20..3ee76d57 100644 --- a/src/gs-entbase/shared/NSVehicle.h +++ b/src/gs-entbase/shared/NSVehicle.h @@ -41,7 +41,7 @@ class NSVehicle:NSSurfacePropEntity virtual bool(void) IsLocalDriver; virtual void(void) PredictPreFrame; virtual void(void) PredictPostFrame; - virtual void(float, float) ReadEntity; + virtual void(float, float) ReceiveEntity; virtual void(void) UpdateView; virtual bool(void) HideViewWeapon; virtual bool(void) HideCrosshair; diff --git a/src/gs-entbase/shared/NSVehicle.qc b/src/gs-entbase/shared/NSVehicle.qc index 05d57ee9..e4345195 100644 --- a/src/gs-entbase/shared/NSVehicle.qc +++ b/src/gs-entbase/shared/NSVehicle.qc @@ -104,7 +104,7 @@ NSVehicle::PredictPostFrame(void) } void -NSVehicle::ReadEntity(float fChanged, float new) +NSVehicle::ReceiveEntity(float fChanged, float new) { if (fChanged & VEHFL_CHANGED_ORIGIN) { origin[0] = readcoord(); @@ -416,6 +416,6 @@ basevehicle_readentity(float isnew) if (isnew) spawnfunc_NSVehicle(); - veh.ReadEntity(flags, isnew); + veh.ReceiveEntity(flags, isnew); } #endif diff --git a/src/gs-entbase/shared/ambient_generic.qc b/src/gs-entbase/shared/ambient_generic.qc new file mode 100644 index 00000000..071675a4 --- /dev/null +++ b/src/gs-entbase/shared/ambient_generic.qc @@ -0,0 +1,348 @@ +/* + * Copyright (c) 2016-2020 Marco Cawthorne + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER + * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING + * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/*QUAKED ambient_generic (1 1 1) (-8 -8 -8) (8 8 8) AS_ARADIUS AS_SRADIUS AS_MRADIUS AS_LRADIUS AS_SILENT AS_NOTTOGGLED +Plays a sound sample of whatever format the engine is configured to support. + +-------- KEYS -------- +"targetname" : Name +"target" : Target when triggered. +"killtarget" : Target to kill when triggered. +"message" : Sound file to play +"volume" : Playback volume from 0.0 to 1.0 +"pitch" : Playback pitch from 0.0 to 2.0 + +-------- SPAWNFLAGS -------- +AS_ARADIUS : Plays the sound everywhere. Heard by everyone. +AS_SRADIUS : Small playback radius. +AS_MRADIUS : Medium playback radius. +AS_LRADIUS : Large playback radius. +AS_SILENT : Start silent, trigger to make it play! +AS_NOTTOGGLED : Don't toggle playback. When triggered, only play the sample once. + +-------- NOTES -------- +If you want it to loop, you have to give the file itself a loop flag. + +-------- TRIVIA -------- +This entity was introduced in Half-Life (1998). +*/ + +enumflags +{ + AS_ARADIUS, + AS_SRADIUS, + AS_MRADIUS, + AS_LRADIUS, + AS_SILENT, + AS_NOTTOGGLED +}; + +enumflags +{ + AMBIENT_PATH, + AMBIENT_VOLUME, + AMBIENT_RADIUS, + AMBIENT_PITCH, + AMBIENT_ORIGIN +}; + +class ambient_generic:NSPointTrigger +{ + /* networked attributes */ + PREDICTED_STRING(m_strActivePath); + PREDICTED_FLOAT(m_flVolume); + PREDICTED_FLOAT(m_flRadius); + PREDICTED_FLOAT(m_flPitch); + bool m_bToggle; + bool m_bLoops; + + /* spawn values */ + string m_strSpawnPath; + float m_flSpawnVolume; + float m_flSpawnPitch; + + void(void) ambient_generic; + + /* overrides */ +#ifdef SERVER + virtual void(float) Save; + virtual void(string, string) Restore; + virtual void(void) EvaluateEntity; + virtual float(entity, float) SendEntity; + virtual void(void) Respawn; + virtual void(entity, int) UseNormal; + virtual void(entity, int) UseLoop; +#else + virtual void(float, float) ReceiveEntity; +#endif + virtual void(string, string) SpawnKey; + virtual void(void) OnRemoveEntity; +}; + +void +ambient_generic::OnRemoveEntity(void) +{ + sound(this, CHAN_VOICE, "common/null.wav", 0.1f, 0); +} + +#ifdef SERVER +void +ambient_generic::Save(float handle) +{ + SaveString(handle, "activepath", m_strActivePath); + SaveString(handle, "soundpath", m_strSpawnPath); + SaveFloat(handle, "volume", m_flVolume); + SaveFloat(handle, "radius", m_flRadius); + SaveFloat(handle, "pitch", m_flPitch); + SaveInt(handle, "toggleswitch", m_bToggle); + SaveInt(handle, "loop", m_bLoops); + super::Save(handle); +} + +void +ambient_generic::Restore(string strKey, string strValue) +{ + switch (strKey) { + case "loop": + m_bLoops = ReadInt(strValue); + break; + case "toggleswitch": + m_bToggle = ReadInt(strValue); + break; + case "pitch": + m_flPitch = ReadFloat(strValue); + break; + case "radius": + m_flRadius = ReadFloat(strValue); + break; + case "volume": + m_flVolume = ReadFloat(strValue); + break; + case "soundpath": + m_strSpawnPath = ReadString(strValue); + break; + case "activepath": + m_strActivePath = ReadString(strValue); + break; + default: + super::Restore(strKey, strValue); + } +} + +void +ambient_generic::UseNormal(entity act, int state) +{ + dprint(sprintf("Sound once: %S Volume: %f; Radius: %d; Pitch: %d\n", \ + m_strActivePath, m_flVolume, m_flRadius, m_flPitch)); + + sound(this, CHAN_VOICE, m_strActivePath, m_flVolume, m_flRadius, m_flPitch); +} + +void +ambient_generic::UseLoop(entity act, int state) +{ + if (m_bToggle == TRUE) { + dprint(sprintf("^2ambient_generic::^3UseLoop^7: %s stops `%s`\n", + target, m_strActivePath)); + m_strActivePath = "common/null.wav"; + } else { + m_strActivePath = m_strSpawnPath; + dprint(sprintf("^2ambient_generic::^3UseLoop^7: %s plays `%s`\n", + target, m_strActivePath)); + } + + m_bToggle = 1 - m_bToggle; +} + +void +ambient_generic::Respawn(void) +{ + SetSize([0,0,0], [0,0,0]); + SetOrigin(GetSpawnOrigin()); + m_strActivePath = m_strSpawnPath; + m_flPitch = m_flSpawnPitch; + m_flVolume = m_flSpawnVolume; + + /* handle volume */ + if (!m_flSpawnVolume) { + m_flVolume = 1.0f; + } + + /* attenuation */ + if (HasSpawnFlags(AS_ARADIUS)) { + m_flRadius = ATTN_NONE; + } else if (HasSpawnFlags(AS_SRADIUS)) { + m_flRadius = ATTN_IDLE; + } else if (HasSpawnFlags(AS_MRADIUS)) { + m_flRadius = ATTN_STATIC; + } else if (HasSpawnFlags(AS_LRADIUS)) { + m_flRadius = ATTN_NORM; + } else { + m_flRadius = ATTN_STATIC; + } + + pvsflags = PVSF_USEPHS; + + if (HasSpawnFlags(AS_NOTTOGGLED)) { + Trigger = UseNormal; + m_bLoops = false; + } else { + m_bLoops = true; + + /* set our sample up */ + if (HasSpawnFlags(AS_SILENT)) { + m_bToggle = false; + m_strActivePath = "common/null.wav"; + } else { + m_bToggle = true; + m_strActivePath = m_strSpawnPath; + } + + Trigger = UseLoop; + } +} + +void +ambient_generic::EvaluateEntity(void) +{ + if (ATTR_CHANGED(origin)) + SetSendFlags(AMBIENT_ORIGIN); + if (ATTR_CHANGED(m_strActivePath)) + SetSendFlags(AMBIENT_PATH); + if (ATTR_CHANGED(m_flVolume)) + SetSendFlags(AMBIENT_VOLUME); + if (ATTR_CHANGED(m_flRadius)) + SetSendFlags(AMBIENT_RADIUS); + if (ATTR_CHANGED(m_flPitch)) + SetSendFlags(AMBIENT_PITCH); + + SAVE_STATE(origin); + SAVE_STATE(m_strActivePath); + SAVE_STATE(m_flVolume); + SAVE_STATE(m_flRadius); + SAVE_STATE(m_flPitch); +} + +float +ambient_generic::SendEntity(entity ePEnt, float flChanged) +{ + /* only override when we're doing the toggle guff */ + if (m_bLoops == false) + return (0); + + WriteByte(MSG_ENTITY, ENT_AMBIENTSOUND); + WriteFloat(MSG_ENTITY, flChanged); + + if (flChanged & AMBIENT_ORIGIN) { + WriteCoord(MSG_ENTITY, origin[0]); + WriteCoord(MSG_ENTITY, origin[1]); + WriteCoord(MSG_ENTITY, origin[2]); + } + + if (flChanged & AMBIENT_PATH) + WriteString(MSG_ENTITY, m_strActivePath); + if (flChanged & AMBIENT_VOLUME) + WriteFloat(MSG_ENTITY, m_flVolume); + if (flChanged & AMBIENT_RADIUS) + WriteByte(MSG_ENTITY, m_flRadius); + if (flChanged & AMBIENT_PITCH) + WriteFloat(MSG_ENTITY, m_flPitch); + + return (1); +} +#else +void +ambient_generic::ReceiveEntity(float isnew, float flChanged) +{ + if (flChanged & AMBIENT_ORIGIN) { + origin[0] = readcoord(); + origin[1] = readcoord(); + origin[2] = readcoord(); + setsize(this, [0,0,0], [0,0,0]); + setorigin(this, origin); + } + + if (flChanged & AMBIENT_PATH) + m_strActivePath = readstring(); + if (flChanged & AMBIENT_VOLUME) + m_flVolume = readfloat(); + if (flChanged & AMBIENT_RADIUS) + m_flRadius = readbyte(); + if (flChanged & AMBIENT_PITCH) + m_flPitch = readfloat(); + + dprint(sprintf("Sound received: %S Volume: %f; Radius: %d; Pitch: %d\n", m_strActivePath, m_flVolume, m_flRadius, m_flPitch)); + //sound(this, CHAN_VOICE, m_strActivePath, m_flVolume, m_flRadius, m_flPitch); + soundupdate(this, CHAN_VOICE, m_strActivePath, m_flVolume, m_flRadius, m_flPitch, 0, 0); +} +#endif + +void +ambient_generic::SpawnKey(string strKey, string strValue) +{ + switch (strKey) { + case "message": + m_strSpawnPath = strValue; + precache_sound(m_strSpawnPath); + message = __NULL__; + break; + case "volume": + m_flSpawnVolume = stof(strValue); + break; + case "pitch": + m_flSpawnPitch = stof(strValue); + break; + /* backwards compat */ + case "health": + m_flSpawnVolume = stof(strValue) * 0.1f; + break; + /* TODO: currently unimplemented */ + case "preset": + case "volstart": + case "fadein": + case "fadeout": + case "pitchstart": + case "spinup": + case "spindown": + case "lfotype": + case "lforate": + case "lfomodpitch": + case "lfomodvol": + case "cspinup": + break; + default: + super::SpawnKey(strKey, strValue); + break; + } +} + +void +ambient_generic::ambient_generic(void) +{ + super::NSPointTrigger(); +} + +#ifdef CLIENT +void +ambient_generic_ReadEntity(float new) +{ + ambient_generic me = (ambient_generic)self; + if (new) { + spawnfunc_ambient_generic(); + } + me.ReceiveEntity(new, readfloat()); +} +#endif diff --git a/src/gs-entbase/shared/env_fog_controller.qc b/src/gs-entbase/shared/env_fog_controller.qc index 7227a468..f35ec019 100644 --- a/src/gs-entbase/shared/env_fog_controller.qc +++ b/src/gs-entbase/shared/env_fog_controller.qc @@ -89,7 +89,7 @@ env_fog_controller:NSPointTrigger virtual float(void) FogRender; virtual void(void) FogUpdate; virtual void(void) RendererRestarted; - virtual void(float,float) ReadEntity; + virtual void(float,float) ReceiveEntity; #else /* main spawn attributes */ int m_iSpawnEnable; @@ -200,7 +200,7 @@ env_fog_controller::RendererRestarted(void) } void -env_fog_controller::ReadEntity(float flSendFlags, float flNew) +env_fog_controller::ReceiveEntity(float flSendFlags, float flNew) { if (flSendFlags & ENVFOG_CHANGED_ACTIVE) m_iFogActive = readbyte(); @@ -539,6 +539,6 @@ env_fog_controller_readentity(float isnew) if (isnew) spawnfunc_env_fog_controller(); - fog.ReadEntity(flags, isnew); + fog.ReceiveEntity(flags, isnew); } #endif diff --git a/src/gs-entbase/shared/func_tankmortar.qc b/src/gs-entbase/shared/func_tankmortar.qc index a200faa8..bc98f259 100644 --- a/src/gs-entbase/shared/func_tankmortar.qc +++ b/src/gs-entbase/shared/func_tankmortar.qc @@ -90,7 +90,7 @@ class func_tankmortar:NSVehicle #ifdef CLIENT virtual void(void) PredictPreFrame; virtual void(void) PredictPostFrame; - virtual void(float, float) ReadEntity; + virtual void(float, float) ReceiveEntity; virtual void(void) UpdateView; #else virtual float(entity, float) SendEntity; @@ -123,7 +123,7 @@ func_tankmortar::PredictPostFrame(void) } void -func_tankmortar::ReadEntity(float fChanged, float new) +func_tankmortar::ReceiveEntity(float fChanged, float new) { if (fChanged & VEHFL_CHANGED_ORIGIN) { origin[0] = readcoord(); @@ -457,6 +457,6 @@ func_tankmortar_readentity(float isnew) if (isnew) spawnfunc_func_tankmortar(); - veh.ReadEntity(flags, isnew); + veh.ReceiveEntity(flags, isnew); } #endif diff --git a/src/gs-entbase/shared/prop_rope.qc b/src/gs-entbase/shared/prop_rope.qc index 13727000..c794b27d 100644 --- a/src/gs-entbase/shared/prop_rope.qc +++ b/src/gs-entbase/shared/prop_rope.qc @@ -61,7 +61,7 @@ class prop_rope:NSEntity #ifdef CLIENT virtual float() predraw; - virtual void(float,float) ReadEntity; + virtual void(float,float) ReceiveEntity; virtual void(vector, vector, vector) DrawSegment; #else virtual void(void) Respawn; @@ -146,7 +146,7 @@ prop_rope::predraw(void) } void -prop_rope::ReadEntity(float flSendFlags, float new) +prop_rope::ReceiveEntity(float flSendFlags, float new) { if (flSendFlags & PROPROPE_CHANGED_MAT) m_strShader = readstring(); @@ -301,6 +301,6 @@ prop_rope_readentity(float isnew) if (isnew) spawnfunc_prop_rope(); - rope.ReadEntity(flags, isnew); + rope.ReceiveEntity(flags, isnew); } #endif diff --git a/src/gs-entbase/shared/prop_vehicle_driveable.qc b/src/gs-entbase/shared/prop_vehicle_driveable.qc index 2a9be6c9..604e3c6b 100644 --- a/src/gs-entbase/shared/prop_vehicle_driveable.qc +++ b/src/gs-entbase/shared/prop_vehicle_driveable.qc @@ -110,7 +110,7 @@ class prop_vehicle_driveable:NSVehicle virtual bool(void) HideViewWeapon; virtual void(void) PredictPreFrame; virtual void(void) PredictPostFrame; - virtual void(float, float) ReadEntity; + virtual void(float, float) ReceiveEntity; virtual void(void) UpdateView; #else virtual void(void) Respawn; @@ -684,7 +684,7 @@ prop_vehicle_driveable::Respawn(void) #ifdef CLIENT void -prop_vehicle_driveable::ReadEntity(float flSendFlags, float flNew) +prop_vehicle_driveable::ReceiveEntity(float flSendFlags, float flNew) { if (flSendFlags & VEHFL_DRIVER) { driver_entnum = readentitynum(); @@ -899,6 +899,6 @@ prop_vehicle_driveable_readentity(float isnew) if (isnew) spawnfunc_prop_vehicle_driveable(); - veh.ReadEntity(flags, isnew); + veh.ReceiveEntity(flags, isnew); } #endif diff --git a/src/server/mapcycle.qc b/src/server/mapcycle.qc index 7ba7d6d0..0462ad9b 100644 --- a/src/server/mapcycle.qc +++ b/src/server/mapcycle.qc @@ -43,11 +43,9 @@ Mapcycle_Init(void) /* read the lines in, see if the map exists and define an enumerated alias */ while ((temp = fgets(fs_mapcycle))) { -#if 0 - print(sprintf( "checking for map %s whichpack result: %S\n", strcat("maps/", temp, ".bsp"), whichpack(strcat("maps/", temp, ".bsp")))); - if (!whichpack(strcat("maps/", temp, ".bsp"))) + if not (whichpack(strcat("maps/", temp, ".bsp"))) continue; -#endif + readcmd(sprintf("alias m%i \"map %s;alias nextmap m%i\"\n", mapcount, temp, mapcount + 1i)); if (mapname == lastmap) diff --git a/src/server/vote.qc b/src/server/vote.qc index e57f15bc..a03f3af9 100644 --- a/src/server/vote.qc +++ b/src/server/vote.qc @@ -258,10 +258,10 @@ CSEv_CallVote_s(string text) tokenize(text); switch (argv(0)) { case "map": - /*if (whichpack(sprintf("maps/%s.bsp", argv(1))) == __NULL__) { + if not (whichpack(sprintf("maps/%s.bsp", argv(1)))) { sprint(self, PRINT_CHAT, sprintf("Map '%s' not available on server.\n", argv(1))); break; - }*/ + } case "kick": case "slowmo": case "timelimit": diff --git a/src/shared/defs.h b/src/shared/defs.h index ebf26cad..7c054f77 100644 --- a/src/shared/defs.h +++ b/src/shared/defs.h @@ -19,10 +19,12 @@ #define PREDICTED_FLOAT(x) float x; float x ##_net #define PREDICTED_VECTOR(x) vector x; vector x ##_net #define PREDICTED_ENT(x) entity x; entity x ##_net +#define PREDICTED_STRING(x) string x; string x ##_net #define PREDICTED_INT_N(x) int x ##_net #define PREDICTED_FLOAT_N(x) float x ##_net #define PREDICTED_VECTOR_N(x) vector x ##_net +#define PREDICTED_STRING_N(x) string x ##_net #define ROLL_BACK(x) x = x ##_net #define SAVE_STATE(x) x ##_net = x