Bugfix: leak entities from weaponbox when touched grenades. (bug by valve)

CZBot: the check cvar bot_deathmath on every time.
This commit is contained in:
s1lentq 2016-07-20 23:44:00 +07:00
parent aa8a8e7bb1
commit baac9cbad4
24 changed files with 132 additions and 195 deletions

View File

@ -136,7 +136,7 @@ public:
Activity m_IdealActivity; // monster should switch to this activity
int m_LastHitGroup; // the last body region that took damage
int m_bitsDamageType; // what types of damage has monster (player) taken
BYTE m_rgbTimeBasedDamage[8];
byte m_rgbTimeBasedDamage[8];
MONSTERSTATE m_MonsterState; // monster's current state
MONSTERSTATE m_IdealMonsterState; // monster should change to this state

View File

@ -1028,12 +1028,6 @@ void CCSBotManager::ValidateMapData()
m_zoneCount = 0;
m_gameScenario = SCENARIO_DEATHMATCH;
#ifdef REGAMEDLL_ADD
// if we have included deathmatch mode, so set the game type like SCENARIO_DEATHMATCH
if (cv_bot_deathmatch.value > 0.0f)
return;
#endif
// Search all entities in the map and set the game type and store all zones (bomb target, etc).
CBaseEntity *entity = NULL;
int i;

View File

@ -105,7 +105,16 @@ public:
SCENARIO_RESCUE_HOSTAGES,
SCENARIO_ESCORT_VIP
};
GameScenarioType GetScenario() const { return m_gameScenario; }
GameScenarioType GetScenario() const
{
#ifdef REGAMEDLL_ADD
// if we have included deathmatch mode, so set the game type like SCENARIO_DEATHMATCH
if (cv_bot_deathmatch.value > 0)
return SCENARIO_DEATHMATCH;
#endif
return m_gameScenario;
}
// "zones"
// depending on the game mode, these are bomb zones, rescue zones, etc.
@ -216,12 +225,12 @@ public:
enum SkillType { LOW, AVERAGE, HIGH, RANDOM };
const char *GetRandomBotName(SkillType skill);
static void MonitorBotCVars();
static void MaintainBotQuota();
static bool AddBot(const BotProfile *profile, BotProfileTeamType team);
void MonitorBotCVars();
void MaintainBotQuota();
bool AddBot(const BotProfile *profile, BotProfileTeamType team);
#define FROM_CONSOLE true
static bool BotAddCommand(BotProfileTeamType team, bool isFromConsole = false); // process the "bot_add" console command
bool BotAddCommand(BotProfileTeamType team, bool isFromConsole = false); // process the "bot_add" console command
#ifndef HOOK_GAMEDLL
private:

View File

@ -647,10 +647,10 @@ public:
BOOL m_fRotating;
string_t m_strChangeTarget;
locksound_t m_ls;
BYTE m_bLockedSound;
BYTE m_bLockedSentence;
BYTE m_bUnlockedSound;
BYTE m_bUnlockedSentence;
byte m_bLockedSound;
byte m_bLockedSentence;
byte m_bUnlockedSound;
byte m_bUnlockedSentence;
int m_sounds;
};

View File

@ -94,17 +94,17 @@ public:
void EXPORT DoorHitBottom();
public:
BYTE m_bHealthValue; // some doors are medi-kit doors, they give players health
byte m_bHealthValue; // some doors are medi-kit doors, they give players health
BYTE m_bMoveSnd; // sound a door makes while moving
BYTE m_bStopSnd; // sound a door makes when it stops
byte m_bMoveSnd; // sound a door makes while moving
byte m_bStopSnd; // sound a door makes when it stops
locksound_t m_ls; // door lock sounds
BYTE m_bLockedSound; // ordinals from entity selection
BYTE m_bLockedSentence;
BYTE m_bUnlockedSound;
BYTE m_bUnlockedSentence;
byte m_bLockedSound; // ordinals from entity selection
byte m_bLockedSentence;
byte m_bUnlockedSound;
byte m_bUnlockedSentence;
float m_lastBlockedTimestamp;
};
@ -151,7 +151,7 @@ public:
public:
static TYPEDESCRIPTION IMPL(m_SaveData)[1];
BYTE m_bMoveSnd; // sound a door makes while moving
byte m_bMoveSnd; // sound a door makes while moving
};
void PlayLockSounds(entvars_t *pev, locksound_t *pls, int flocked, int fbutton);

View File

@ -73,8 +73,8 @@ typedef struct locksounds
int iUnlockedSentence;
float flwaitSound;
float flwaitSentence;
BYTE bEOFLocked;
BYTE bEOFUnlocked;
byte bEOFLocked;
byte bEOFUnlocked;
} locksound_t;

View File

@ -523,15 +523,20 @@ CHalfLifeMultiplay::CHalfLifeMultiplay()
}
else
{
// 3/31/99
// Added lservercfg file cvar, since listen and dedicated servers should not
// share a single config file. (sjb)
// listen server
CVAR_SET_FLOAT("pausable", 0);
const char *lservercfgfile = CVAR_GET_STRING("lservercfgfile");
if (lservercfgfile && *lservercfgfile != '\0')
if (lservercfgfile && lservercfgfile[0] != '\0')
{
ALERT(at_console, "Executing listen server config file\n");
char szCommand[256];
ALERT(at_console, "Executing listen server config file\n");
Q_sprintf(szCommand, "exec %s\n", lservercfgfile);
SERVER_COMMAND(szCommand);
}
@ -594,15 +599,32 @@ CHalfLifeMultiplay::CHalfLifeMultiplay()
void CHalfLifeMultiplay::__MAKE_VHOOK(RefreshSkillData)()
{
// load all default values
CGameRules::RefreshSkillData();
// override some values for multiplay.
// Glock Round
gSkillData.plrDmg9MM = 12;
// MP5 Round
gSkillData.plrDmgMP5 = 12;
// suitcharger
gSkillData.suitchargerCapacity = 30;
// 357 Round
gSkillData.plrDmg357 = 40;
// M203 grenade
gSkillData.plrDmgM203Grenade = 100;
// Shotgun buckshot
// fewer pellets in deathmatch
gSkillData.plrDmgBuckshot = 20;
// Crossbow
gSkillData.plrDmgCrossbowClient = 20;
// RPG
gSkillData.plrDmgRPG = 120;
}
@ -3375,7 +3397,6 @@ void CHalfLifeMultiplay::__MAKE_VHOOK(PlayerThink)(CBasePlayer *pPlayer)
if (pPlayer->m_pActiveItem && pPlayer->m_pActiveItem->IsWeapon())
{
CBasePlayerWeapon *pWeapon = static_cast<CBasePlayerWeapon *>(pPlayer->m_pActiveItem->GetWeaponPtr());
if (pWeapon->m_iWeaponState & WPNSTATE_SHIELD_DRAWN)
{
pPlayer->m_bCanShoot = false;

View File

@ -87,12 +87,12 @@ void CBasePlatTrain::__MAKE_VHOOK(KeyValue)(KeyValueData *pkvd)
}
else if (FStrEq(pkvd->szKeyName, "movesnd"))
{
m_bMoveSnd = (BYTE)Q_atof(pkvd->szValue);
m_bMoveSnd = (byte)Q_atof(pkvd->szValue);
pkvd->fHandled = TRUE;
}
else if (FStrEq(pkvd->szKeyName, "stopsnd"))
{
m_bStopSnd = (BYTE)Q_atof(pkvd->szValue);
m_bStopSnd = (byte)Q_atof(pkvd->szValue);
pkvd->fHandled = TRUE;
}
else if (FStrEq(pkvd->szKeyName, "volume"))

View File

@ -70,8 +70,8 @@ public:
public:
static TYPEDESCRIPTION IMPL(m_SaveData)[3];
BYTE m_bMoveSnd;
BYTE m_bStopSnd;
byte m_bMoveSnd;
byte m_bStopSnd;
float m_volume;
};

View File

@ -1427,6 +1427,7 @@ void packPlayerItem(CBasePlayer *pPlayer, CBasePlayerItem *pItem, bool packAmmo)
{
pWeaponBox->PackAmmo(MAKE_STRING(IMPL_CLASS(CBasePlayerItem, ItemInfoArray)[pItem->m_iId].pszAmmo1), pPlayer->m_rgAmmo[pItem->PrimaryAmmoIndex()]);
}
SET_MODEL(ENT(pWeaponBox->pev), modelName);
}
}
@ -1489,6 +1490,7 @@ void packPlayerNade(CBasePlayer *pPlayer, CBasePlayerItem *pItem, bool packAmmo)
{
pWeaponBox->PackAmmo(MAKE_STRING(IMPL_CLASS(CBasePlayerItem, ItemInfoArray)[pItem->m_iId].pszAmmo1), pPlayer->m_rgAmmo[pItem->PrimaryAmmoIndex()]);
}
SET_MODEL(ENT(pWeaponBox->pev), modelName);
}
}
@ -1520,7 +1522,7 @@ void CBasePlayer::PackDeadPlayerItems()
// there's a weapon here. Should I pack it?
CBasePlayerItem *pPlayerItem = m_rgpPlayerItems[ n ];
while (pPlayerItem != NULL)
while (pPlayerItem)
{
ItemInfo info;
if (pPlayerItem->iItemSlot() < KNIFE_SLOT && !bShieldDropped)
@ -1671,7 +1673,7 @@ void CBasePlayer::RemoveAllItems(BOOL removeSuit)
{
m_pActiveItem = m_rgpPlayerItems[i];
while (m_pActiveItem != NULL)
while (m_pActiveItem)
{
CBasePlayerItem *pPendingItem = m_pActiveItem->m_pNext;
@ -4479,7 +4481,7 @@ void EXT_FUNC CBasePlayer::__API_VHOOK(PreThink)()
void CBasePlayer::CheckTimeBasedDamage()
{
int i;
BYTE bDuration = 0;
byte bDuration = 0;
static float gtbdPrev = 0.0;
if (!(m_bitsDamageType & DMG_TIMEBASED))
@ -4573,7 +4575,7 @@ void CBasePlayer::CheckTimeBasedDamage()
void CBasePlayer::UpdateGeigerCounter()
{
BYTE range;
byte range;
// delay per update ie: don't flood net with these msgs
if (gpGlobals->time < m_flgeigerDelay)
@ -6356,7 +6358,7 @@ LINK_HOOK_CLASS_CHAIN(BOOL, CBasePlayer, AddPlayerItem, (CBasePlayerItem *pItem)
BOOL CBasePlayer::__API_VHOOK(AddPlayerItem)(CBasePlayerItem *pItem)
{
CBasePlayerItem *pInsert = m_rgpPlayerItems[ pItem->iItemSlot() ];
while (pInsert != NULL)
while (pInsert)
{
if (FClassnameIs(pInsert->pev, STRING(pItem->pev->classname)))
{

View File

@ -1162,7 +1162,7 @@ int CBasePlayerWeapon::__MAKE_VHOOK(UpdateClientData)(CBasePlayer *pPlayer)
pPlayer->m_fWeapon = TRUE;
}
if (m_pNext != NULL)
if (m_pNext)
{
m_pNext->UpdateClientData(pPlayer);
}
@ -1629,7 +1629,7 @@ void CWeaponBox::Kill()
{
pWeapon = m_rgpPlayerItems[i];
while (pWeapon != NULL)
while (pWeapon)
{
pWeapon->SetThink(&CBaseEntity::SUB_Remove);
pWeapon->pev->nextthink = gpGlobals->time + 0.1f;
@ -1678,14 +1678,12 @@ void CWeaponBox::__MAKE_VHOOK(Touch)(CBaseEntity *pOther)
for (int i = 0; i < MAX_ITEM_TYPES; ++i)
{
if (!m_rgpPlayerItems[i])
{
continue;
}
CBasePlayerItem *pItem = m_rgpPlayerItems[i];
// have at least one weapon in this slot
while (pItem != NULL)
while (pItem)
{
if ((pPlayer->HasShield() && pItem->m_iId == WEAPON_ELITE)
|| (pPlayer->IsBot() && (TheCSBots() != NULL && !TheCSBots()->IsWeaponUseable(pItem))))
@ -1697,6 +1695,7 @@ void CWeaponBox::__MAKE_VHOOK(Touch)(CBaseEntity *pOther)
if (pPlayer->HasRestrictItem((pItem->m_iId == WEAPON_SHIELDGUN) ? ITEM_SHIELDGUN : (ItemID)pItem->m_iId, ITEM_TYPE_TOUCHED))
return;
#endif
if (FClassnameIs(pItem->pev, "weapon_c4"))
{
#ifdef REGAMEDLL_FIXES
@ -1734,8 +1733,7 @@ void CWeaponBox::__MAKE_VHOOK(Touch)(CBaseEntity *pOther)
pPlayer->SetBombIcon(FALSE);
pPlayer->pev->body = 1;
CBaseEntity *pEntity = NULL;
CBaseEntity *pEntity = nullptr;
while ((pEntity = UTIL_FindEntityByClassname(pEntity, "player")) != NULL)
{
if (FNullEnt(pEntity->edict()))
@ -1761,29 +1759,48 @@ void CWeaponBox::__MAKE_VHOOK(Touch)(CBaseEntity *pOther)
}
}
if (TheCSBots() != NULL)
if (TheCSBots())
{
TheCSBots()->SetLooseBomb(NULL);
}
if (TheBots != NULL)
if (TheBots)
{
TheBots->OnEvent(EVENT_BOMB_PICKED_UP, pPlayer);
}
}
if (i >= PRIMARY_WEAPON_SLOT && i <= PISTOL_SLOT && pPlayer->m_rgpPlayerItems[i] != NULL)
if (i >= PRIMARY_WEAPON_SLOT && i <= PISTOL_SLOT && pPlayer->m_rgpPlayerItems[i])
{
// ...
}
else if (i == GRENADE_SLOT)
{
CBasePlayerWeapon *pGrenade = static_cast<CBasePlayerWeapon *>(m_rgpPlayerItems[i]);
if (pGrenade != NULL && pGrenade->IsWeapon())
if (pGrenade && pGrenade->IsWeapon())
{
int playerGrenades = pPlayer->m_rgAmmo[pGrenade->m_iPrimaryAmmoType];
#ifdef REGAMEDLL_FIXES
auto info = GetWeaponInfo(pGrenade->m_iId);
if (info && playerGrenades < info->maxRounds)
{
auto pNext = m_rgpPlayerItems[i]->m_pNext;
if (pPlayer->AddPlayerItem(pItem))
{
pItem->AttachToPlayer(pPlayer);
bEmitSound = true;
}
// unlink this weapon from the box
m_rgpPlayerItems[i] = pItem = pNext;
continue;
}
#else
int maxGrenades = 0;
const char *grenadeName = NULL;
const char *grenadeName = nullptr;
switch (pGrenade->m_iId)
{
@ -1801,10 +1818,12 @@ void CWeaponBox::__MAKE_VHOOK(Touch)(CBaseEntity *pOther)
break;
}
if (playerGrenades < maxGrenades && grenadeName != NULL)
if (playerGrenades < maxGrenades && grenadeName)
{
// CRITICAL BUG: since gives a new entity using GiveNamedItem,
// but the entity is packaged in a weaponbox still exists and will never used or removed. It's leak!
bEmitSound = true;
pPlayer->GiveNamedItemEx(grenadeName);
pPlayer->GiveNamedItem(grenadeName);
// unlink this weapon from the box
pItem = m_rgpPlayerItems[i]->m_pNext;
@ -1812,6 +1831,8 @@ void CWeaponBox::__MAKE_VHOOK(Touch)(CBaseEntity *pOther)
continue;
}
#endif
}
}
else if (pPlayer->HasShield() && i == PRIMARY_WEAPON_SLOT)
@ -1820,6 +1841,7 @@ void CWeaponBox::__MAKE_VHOOK(Touch)(CBaseEntity *pOther)
}
else
{
auto pNext = m_rgpPlayerItems[i]->m_pNext;
if (pPlayer->AddPlayerItem(pItem))
{
pItem->AttachToPlayer(pPlayer);
@ -1827,8 +1849,7 @@ void CWeaponBox::__MAKE_VHOOK(Touch)(CBaseEntity *pOther)
}
// unlink this weapon from the box
pItem = m_rgpPlayerItems[i]->m_pNext;
m_rgpPlayerItems[i] = pItem;
m_rgpPlayerItems[i] = pItem = pNext;
continue;
}
@ -1845,11 +1866,6 @@ void CWeaponBox::__MAKE_VHOOK(Touch)(CBaseEntity *pOther)
{
if (!FStringNull(m_rgiszAmmo[n]))
{
#ifdef REGAMEDLL_FIXES
if (FStrEq(STRING(m_rgiszAmmo[n]), "Flashbang") && m_rgAmmo[n] < MaxAmmoCarry(m_rgiszAmmo[n]))
continue;
#endif
// there's some ammo of this type.
pPlayer->GiveAmmo(m_rgAmmo[n], (char *)STRING(m_rgiszAmmo[n]), MaxAmmoCarry(m_rgiszAmmo[n]));

View File

@ -642,10 +642,16 @@ void CWorld::__MAKE_VHOOK(Precache)()
}
}
if (pev->spawnflags & SF_WORLD_DARK)
CVAR_SET_FLOAT("v_dark", 1);
else
CVAR_SET_FLOAT("v_dark", 0);
#ifndef REGAMEDLL_FIXES
if (!IS_DEDICATED_SERVER())
#endif
{
// TODO: cvar v_dark there is only the client side
if (pev->spawnflags & SF_WORLD_DARK)
CVAR_SET_FLOAT("v_dark", 1);
else
CVAR_SET_FLOAT("v_dark", 0);
}
if (pev->spawnflags & SF_WORLD_TITLE)
{

View File

@ -10,7 +10,7 @@ void CHEGrenade::__MAKE_VHOOK(Spawn)()
SET_MODEL(edict(), "models/w_hegrenade.mdl");
pev->dmg = 4;
m_iDefaultAmmo = HEGRENADE_DEFAULT_GIVE;
m_flStartThrow = 0;
m_flReleaseThrow = -1.0f;

View File

@ -95,7 +95,7 @@ typedef struct
int fEnabled;
int fPlayLooping;
float cdvolume;
//BYTE remap[100];
//byte remap[100];
int fCDRom;
int fPlayTrack;
} CDStatus;

View File

@ -88,7 +88,7 @@ public:
Activity m_IdealActivity; // monster should switch to this activity
int m_LastHitGroup; // the last body region that took damage
int m_bitsDamageType; // what types of damage has monster (player) taken
BYTE m_rgbTimeBasedDamage[8];
byte m_rgbTimeBasedDamage[8];
MONSTERSTATE m_MonsterState; // monster's current state
MONSTERSTATE m_IdealMonsterState; // monster should change to this state

View File

@ -282,10 +282,10 @@ public:
locksound_t m_ls; // door lock sounds
BYTE m_bLockedSound; // ordinals from entity selection
BYTE m_bLockedSentence;
BYTE m_bUnlockedSound;
BYTE m_bUnlockedSentence;
byte m_bLockedSound; // ordinals from entity selection
byte m_bLockedSentence;
byte m_bUnlockedSound;
byte m_bUnlockedSentence;
int m_sounds;
};

View File

@ -56,17 +56,17 @@ public:
virtual void Use(CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value) = 0;
virtual void Blocked(CBaseEntity *pOther) = 0;
public:
BYTE m_bHealthValue; // some doors are medi-kit doors, they give players health
byte m_bHealthValue; // some doors are medi-kit doors, they give players health
BYTE m_bMoveSnd; // sound a door makes while moving
BYTE m_bStopSnd; // sound a door makes when it stops
byte m_bMoveSnd; // sound a door makes while moving
byte m_bStopSnd; // sound a door makes when it stops
locksound_t m_ls; // door lock sounds
BYTE m_bLockedSound; // ordinals from entity selection
BYTE m_bLockedSentence;
BYTE m_bUnlockedSound;
BYTE m_bUnlockedSentence;
byte m_bLockedSound; // ordinals from entity selection
byte m_bLockedSentence;
byte m_bUnlockedSound;
byte m_bUnlockedSentence;
float m_lastBlockedTimestamp;
};
@ -88,5 +88,5 @@ public:
virtual int ObjectCaps() = 0;
virtual void Use(CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value) = 0;
public:
BYTE m_bMoveSnd; // sound a door makes while moving
byte m_bMoveSnd; // sound a door makes while moving
};

View File

@ -75,8 +75,8 @@ typedef struct locksounds
int iUnlockedSentence;
float flwaitSound;
float flwaitSentence;
BYTE bEOFLocked;
BYTE bEOFUnlocked;
byte bEOFLocked;
byte bEOFUnlocked;
} locksound_t;

View File

@ -52,8 +52,8 @@ public:
// This is done to fix spawn flag collisions between this class and a derived class
virtual BOOL IsTogglePlat() = 0;
public:
BYTE m_bMoveSnd;
BYTE m_bStopSnd;
byte m_bMoveSnd;
byte m_bStopSnd;
float m_volume;
};

View File

@ -94,7 +94,7 @@ typedef struct
int fEnabled;
int fPlayLooping;
float cdvolume;
//BYTE remap[100];
//byte remap[100];
int fCDRom;
int fPlayTrack;
} CDStatus;

View File

@ -70,8 +70,6 @@ typedef unsigned char BYTE;
typedef unsigned char byte;
typedef unsigned short word;
#include "string_t.h"
typedef float vec_t;

View File

@ -70,8 +70,6 @@ typedef unsigned char BYTE;
typedef unsigned char byte;
typedef unsigned short word;
#include "string_t.h"
typedef float vec_t;

View File

@ -1,106 +0,0 @@
//========= Copyright (c) 1996-2002, Valve LLC, All rights reserved. ==========
//
// Purpose: Defines the more complete set of operations on the string_t defined
// These should be used instead of direct manipulation to allow more
// flexibility in future ports or optimization.
//
// $NoKeywords: $
//=============================================================================
#ifndef STRING_T_H
#define STRING_T_H
#if defined( _WIN32 )
#pragma once
#endif
#include "osconfig.h"
#include <stdio.h>
#ifndef NO_STRING_T
#ifdef WEAK_STRING_T
typedef int valve_string_t;
//-----------------------------------------------------------------------------
// Purpose: The correct way to specify the NULL string as a constant.
//-----------------------------------------------------------------------------
#define NULL_STRING 0
//-----------------------------------------------------------------------------
// Purpose: Given a string_t, make a C string. By convention the result string
// pointer should be considered transient and should not be stored.
//-----------------------------------------------------------------------------
#define STRING( offset ) ( ( offset ) ? reinterpret_cast<const char *>( offset ) : "" )
//-----------------------------------------------------------------------------
// Purpose: Given a C string, obtain a string_t
//-----------------------------------------------------------------------------
#define MAKE_STRING( str ) ( ( *str != 0 ) ? reinterpret_cast<int>( str ) : 0 )
//-----------------------------------------------------------------------------
#else // Strong string_t
//-----------------------------------------------------------------------------
struct valve_string_t
{
public:
bool operator!() const { return (pszValue == NULL); }
bool operator==(const valve_string_t &rhs) const { return (pszValue == rhs.pszValue); }
bool operator!=(const valve_string_t &rhs) const { return (pszValue != rhs.pszValue); }
const char *ToCStr() const { return (pszValue) ? pszValue : ""; }
protected:
const char *pszValue;
};
//-----------------------------------------------------------------------------
struct castable_string_t : public valve_string_t // string_t is used in unions, hence, no constructor allowed
{
castable_string_t() { pszValue = NULL; }
castable_string_t(const char *pszFrom) { pszValue = (*pszFrom != 0) ? pszFrom : 0; }
};
//-----------------------------------------------------------------------------
// Purpose: The correct way to specify the NULL string as a constant.
//-----------------------------------------------------------------------------
#define NULL_STRING castable_string_t()
//-----------------------------------------------------------------------------
// Purpose: Given a string_t, make a C string. By convention the result string
// pointer should be considered transient and should not be stored.
//-----------------------------------------------------------------------------
#define STRING( string_t_obj ) (string_t_obj).ToCStr()
//-----------------------------------------------------------------------------
// Purpose: Given a C string, obtain a string_t
//-----------------------------------------------------------------------------
#define MAKE_STRING( c_str ) castable_string_t( c_str )
//-----------------------------------------------------------------------------
#endif
#else // NO_STRING_T
typedef const char *string_t;
#define NULL_STRING 0
#define STRING( c_str ) ( c_str )
#define MAKE_STRING( c_str ) ( c_str )
#endif // NO_STRING_T
//=============================================================================
#endif // STRING_T_H

View File

@ -3,7 +3,6 @@
#include "version/appversion.h"
#include "osconfig.h"
#include "basetypes.h"
#include "archtypes.h"
#include "sse_mathfun.h"