summaryrefslogtreecommitdiff
path: root/src/game/g_spawn.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/game/g_spawn.c')
-rw-r--r--src/game/g_spawn.c699
1 files changed, 699 insertions, 0 deletions
diff --git a/src/game/g_spawn.c b/src/game/g_spawn.c
new file mode 100644
index 0000000..b956474
--- /dev/null
+++ b/src/game/g_spawn.c
@@ -0,0 +1,699 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2006 Tim Angus
+
+This file is part of Tremulous.
+
+Tremulous is free software; you can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+Tremulous is distributed in the hope that it will be
+useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+#include "g_local.h"
+
+qboolean G_SpawnString( const char *key, const char *defaultString, char **out )
+{
+ int i;
+
+ if( !level.spawning )
+ {
+ *out = (char *)defaultString;
+// G_Error( "G_SpawnString() called while not spawning" );
+ }
+
+ for( i = 0; i < level.numSpawnVars; i++ )
+ {
+ if( !Q_stricmp( key, level.spawnVars[ i ][ 0 ] ) )
+ {
+ *out = level.spawnVars[ i ][ 1 ];
+ return qtrue;
+ }
+ }
+
+ *out = (char *)defaultString;
+ return qfalse;
+}
+
+qboolean G_SpawnFloat( const char *key, const char *defaultString, float *out )
+{
+ char *s;
+ qboolean present;
+
+ present = G_SpawnString( key, defaultString, &s );
+ *out = atof( s );
+ return present;
+}
+
+qboolean G_SpawnInt( const char *key, const char *defaultString, int *out )
+{
+ char *s;
+ qboolean present;
+
+ present = G_SpawnString( key, defaultString, &s );
+ *out = atoi( s );
+ return present;
+}
+
+qboolean G_SpawnVector( const char *key, const char *defaultString, float *out )
+{
+ char *s;
+ qboolean present;
+
+ present = G_SpawnString( key, defaultString, &s );
+ sscanf( s, "%f %f %f", &out[ 0 ], &out[ 1 ], &out[ 2 ] );
+ return present;
+}
+
+qboolean G_SpawnVector4( const char *key, const char *defaultString, float *out )
+{
+ char *s;
+ qboolean present;
+
+ present = G_SpawnString( key, defaultString, &s );
+ sscanf( s, "%f %f %f %f", &out[ 0 ], &out[ 1 ], &out[ 2 ], &out[ 3 ] );
+ return present;
+}
+
+
+
+//
+// fields are needed for spawning from the entity string
+//
+typedef enum
+{
+ F_INT,
+ F_FLOAT,
+ F_LSTRING, // string on disk, pointer in memory, TAG_LEVEL
+ F_GSTRING, // string on disk, pointer in memory, TAG_GAME
+ F_VECTOR,
+ F_VECTOR4, //TA
+ F_ANGLEHACK,
+ F_ENTITY, // index on disk, pointer in memory
+ F_ITEM, // index on disk, pointer in memory
+ F_CLIENT, // index on disk, pointer in memory
+ F_IGNORE
+} fieldtype_t;
+
+typedef struct
+{
+ char *name;
+ int ofs;
+ fieldtype_t type;
+ int flags;
+} field_t;
+
+field_t fields[ ] =
+{
+ {"classname", FOFS(classname), F_LSTRING},
+ {"origin", FOFS(s.origin), F_VECTOR},
+ {"model", FOFS(model), F_LSTRING},
+ {"model2", FOFS(model2), F_LSTRING},
+ {"spawnflags", FOFS(spawnflags), F_INT},
+ {"speed", FOFS(speed), F_FLOAT},
+ {"target", FOFS(target), F_LSTRING},
+ {"targetname", FOFS(targetname), F_LSTRING},
+ {"message", FOFS(message), F_LSTRING},
+ {"team", FOFS(team), F_LSTRING},
+ {"wait", FOFS(wait), F_FLOAT},
+ {"random", FOFS(random), F_FLOAT},
+ {"count", FOFS(count), F_INT},
+ {"health", FOFS(health), F_INT},
+ {"light", 0, F_IGNORE},
+ {"dmg", FOFS(damage), F_INT},
+ {"angles", FOFS(s.angles), F_VECTOR},
+ {"angle", FOFS(s.angles), F_ANGLEHACK},
+ {"bounce", FOFS(physicsBounce), F_FLOAT},
+ {"alpha", FOFS(pos1), F_VECTOR},
+ {"radius", FOFS(pos2), F_VECTOR},
+ {"acceleration", FOFS(acceleration), F_VECTOR},
+ {"animation", FOFS(animation), F_VECTOR4},
+ {"rotatorAngle", FOFS(rotatorAngle), F_FLOAT},
+ {"targetShaderName", FOFS(targetShaderName), F_LSTRING},
+ {"targetShaderNewName", FOFS(targetShaderNewName), F_LSTRING},
+
+ {NULL}
+};
+
+
+typedef struct
+{
+ char *name;
+ void (*spawn)(gentity_t *ent);
+} spawn_t;
+
+void SP_info_player_start( gentity_t *ent );
+void SP_info_player_deathmatch( gentity_t *ent );
+void SP_info_player_intermission( gentity_t *ent );
+
+void SP_info_alien_intermission( gentity_t *ent );
+void SP_info_human_intermission( gentity_t *ent );
+
+void SP_info_firstplace( gentity_t *ent );
+void SP_info_secondplace( gentity_t *ent );
+void SP_info_thirdplace( gentity_t *ent );
+void SP_info_podium( gentity_t *ent );
+
+void SP_func_plat( gentity_t *ent );
+void SP_func_static( gentity_t *ent );
+void SP_func_rotating( gentity_t *ent );
+void SP_func_bobbing( gentity_t *ent );
+void SP_func_pendulum( gentity_t *ent );
+void SP_func_button( gentity_t *ent );
+void SP_func_door( gentity_t *ent );
+void SP_func_door_rotating( gentity_t *ent );
+void SP_func_door_model( gentity_t *ent );
+void SP_func_train( gentity_t *ent );
+void SP_func_timer( gentity_t *self);
+
+void SP_trigger_always( gentity_t *ent );
+void SP_trigger_multiple( gentity_t *ent );
+void SP_trigger_push( gentity_t *ent );
+void SP_trigger_teleport( gentity_t *ent );
+void SP_trigger_hurt( gentity_t *ent );
+void SP_trigger_stage( gentity_t *ent );
+void SP_trigger_win( gentity_t *ent );
+void SP_trigger_buildable( gentity_t *ent );
+void SP_trigger_class( gentity_t *ent );
+void SP_trigger_equipment( gentity_t *ent );
+void SP_trigger_gravity( gentity_t *ent );
+void SP_trigger_heal( gentity_t *ent );
+void SP_trigger_ammo( gentity_t *ent );
+
+void SP_target_delay( gentity_t *ent );
+void SP_target_speaker( gentity_t *ent );
+void SP_target_print( gentity_t *ent );
+void SP_target_character( gentity_t *ent );
+void SP_target_score( gentity_t *ent );
+void SP_target_teleporter( gentity_t *ent );
+void SP_target_relay( gentity_t *ent );
+void SP_target_kill( gentity_t *ent );
+void SP_target_position( gentity_t *ent );
+void SP_target_location( gentity_t *ent );
+void SP_target_push( gentity_t *ent );
+void SP_target_rumble( gentity_t *ent );
+void SP_target_alien_win( gentity_t *ent );
+void SP_target_human_win( gentity_t *ent );
+void SP_target_hurt( gentity_t *ent );
+
+void SP_light( gentity_t *self );
+void SP_info_null( gentity_t *self );
+void SP_info_notnull( gentity_t *self );
+void SP_info_camp( gentity_t *self );
+void SP_path_corner( gentity_t *self );
+
+void SP_misc_teleporter_dest( gentity_t *self );
+void SP_misc_model( gentity_t *ent );
+void SP_misc_portal_camera( gentity_t *ent );
+void SP_misc_portal_surface( gentity_t *ent );
+
+void SP_shooter_rocket( gentity_t *ent );
+void SP_shooter_plasma( gentity_t *ent );
+void SP_shooter_grenade( gentity_t *ent );
+void SP_mine_grenade( gentity_t *ent );//ROTAXfun
+void SP_flare_grenade( gentity_t *ent );//ROTAXfun
+
+void SP_misc_particle_system( gentity_t *ent );
+void SP_misc_anim_model( gentity_t *ent );
+void SP_misc_light_flare( gentity_t *ent );
+
+spawn_t spawns[ ] =
+{
+ // info entities don't do anything at all, but provide positional
+ // information for things controlled by other processes
+ { "info_player_start", SP_info_player_start },
+ { "info_player_deathmatch", SP_info_player_deathmatch },
+ { "info_player_intermission", SP_info_player_intermission },
+
+ //TA: extra bits
+ { "info_alien_intermission", SP_info_alien_intermission },
+ { "info_human_intermission", SP_info_human_intermission },
+
+ { "info_null", SP_info_null },
+ { "info_notnull", SP_info_notnull }, // use target_position instead
+
+ { "func_plat", SP_func_plat },
+ { "func_button", SP_func_button },
+ { "func_door", SP_func_door },
+ { "func_door_rotating", SP_func_door_rotating }, //TA
+ { "func_door_model", SP_func_door_model }, //TA
+ { "func_static", SP_func_static },
+ { "func_rotating", SP_func_rotating },
+ { "func_bobbing", SP_func_bobbing },
+ { "func_pendulum", SP_func_pendulum },
+ { "func_train", SP_func_train },
+ { "func_group", SP_info_null },
+ { "func_timer", SP_func_timer }, // rename trigger_timer?
+
+ // Triggers are brush objects that cause an effect when contacted
+ // by a living player, usually involving firing targets.
+ // While almost everything could be done with
+ // a single trigger class and different targets, triggered effects
+ // could not be client side predicted (push and teleport).
+ { "trigger_always", SP_trigger_always },
+ { "trigger_multiple", SP_trigger_multiple },
+ { "trigger_push", SP_trigger_push },
+ { "trigger_teleport", SP_trigger_teleport },
+ { "trigger_hurt", SP_trigger_hurt },
+ { "trigger_stage", SP_trigger_stage },
+ { "trigger_win", SP_trigger_win },
+ { "trigger_buildable", SP_trigger_buildable },
+ { "trigger_class", SP_trigger_class },
+ { "trigger_equipment", SP_trigger_equipment },
+ { "trigger_gravity", SP_trigger_gravity },
+ { "trigger_heal", SP_trigger_heal },
+ { "trigger_ammo", SP_trigger_ammo },
+
+ // targets perform no action by themselves, but must be triggered
+ // by another entity
+ { "target_delay", SP_target_delay },
+ { "target_speaker", SP_target_speaker },
+ { "target_print", SP_target_print },
+ { "target_score", SP_target_score },
+ { "target_teleporter", SP_target_teleporter },
+ { "target_relay", SP_target_relay },
+ { "target_kill", SP_target_kill },
+ { "target_position", SP_target_position },
+ { "target_location", SP_target_location },
+ { "target_push", SP_target_push },
+ { "target_rumble", SP_target_rumble },
+ { "target_alien_win", SP_target_alien_win },
+ { "target_human_win", SP_target_human_win },
+ { "target_hurt", SP_target_hurt },
+
+ { "light", SP_light },
+ { "path_corner", SP_path_corner },
+
+ { "misc_teleporter_dest", SP_misc_teleporter_dest },
+ { "misc_model", SP_misc_model },
+ { "misc_portal_surface", SP_misc_portal_surface },
+ { "misc_portal_camera", SP_misc_portal_camera },
+
+ { "misc_particle_system", SP_misc_particle_system },
+ { "misc_anim_model", SP_misc_anim_model },
+ { "misc_light_flare", SP_misc_light_flare },
+
+ { NULL, 0 }
+};
+
+/*
+===============
+G_CallSpawn
+
+Finds the spawn function for the entity and calls it,
+returning qfalse if not found
+===============
+*/
+qboolean G_CallSpawn( gentity_t *ent )
+{
+ spawn_t *s;
+ buildable_t buildable;
+
+ if( !ent->classname )
+ {
+ G_Printf( "G_CallSpawn: NULL classname\n" );
+ return qfalse;
+ }
+
+ //check buildable spawn functions
+ if( ( buildable = BG_FindBuildNumForEntityName( ent->classname ) ) != BA_NONE )
+ {
+ // don't spawn built-in buildings if we are using a custom layout
+ if( level.layout[ 0 ] && Q_stricmp( level.layout, "*BUILTIN*" ) )
+ return qtrue;
+
+ if( buildable == BA_A_SPAWN || buildable == BA_H_SPAWN )
+ {
+ ent->s.angles[ YAW ] += 180.0f;
+ AngleNormalize360( ent->s.angles[ YAW ] );
+ }
+
+ G_SpawnBuildable( ent, buildable );
+ return qtrue;
+ }
+
+ // check normal spawn functions
+ for( s = spawns; s->name; s++ )
+ {
+ if( !strcmp( s->name, ent->classname ) )
+ {
+ // found it
+ s->spawn( ent );
+ return qtrue;
+ }
+ }
+
+ G_Printf( "%s doesn't have a spawn function\n", ent->classname );
+ return qfalse;
+}
+
+/*
+=============
+G_NewString
+
+Builds a copy of the string, translating \n to real linefeeds
+so message texts can be multi-line
+=============
+*/
+char *G_NewString( const char *string )
+{
+ char *newb, *new_p;
+ int i,l;
+
+ l = strlen( string ) + 1;
+
+ newb = G_Alloc( l );
+
+ new_p = newb;
+
+ // turn \n into a real linefeed
+ for( i = 0 ; i < l ; i++ )
+ {
+ if( string[ i ] == '\\' && i < l - 1 )
+ {
+ i++;
+ if( string[ i ] == 'n' )
+ *new_p++ = '\n';
+ else
+ *new_p++ = '\\';
+ }
+ else
+ *new_p++ = string[ i ];
+ }
+
+ return newb;
+}
+
+
+
+
+/*
+===============
+G_ParseField
+
+Takes a key/value pair and sets the binary values
+in a gentity
+===============
+*/
+void G_ParseField( const char *key, const char *value, gentity_t *ent )
+{
+ field_t *f;
+ byte *b;
+ float v;
+ vec3_t vec;
+ vec4_t vec4;
+
+ for( f = fields; f->name; f++ )
+ {
+ if( !Q_stricmp( f->name, key ) )
+ {
+ // found it
+ b = (byte *)ent;
+
+ switch( f->type )
+ {
+ case F_LSTRING:
+ *(char **)( b + f->ofs ) = G_NewString( value );
+ break;
+
+ case F_VECTOR:
+ sscanf( value, "%f %f %f", &vec[ 0 ], &vec[ 1 ], &vec[ 2 ] );
+
+ ( (float *)( b + f->ofs ) )[ 0 ] = vec[ 0 ];
+ ( (float *)( b + f->ofs ) )[ 1 ] = vec[ 1 ];
+ ( (float *)( b + f->ofs ) )[ 2 ] = vec[ 2 ];
+ break;
+
+ case F_VECTOR4:
+ sscanf( value, "%f %f %f %f", &vec4[ 0 ], &vec4[ 1 ], &vec4[ 2 ], &vec4[ 3 ] );
+
+ ( (float *)( b + f->ofs ) )[ 0 ] = vec4[ 0 ];
+ ( (float *)( b + f->ofs ) )[ 1 ] = vec4[ 1 ];
+ ( (float *)( b + f->ofs ) )[ 2 ] = vec4[ 2 ];
+ ( (float *)( b + f->ofs ) )[ 3 ] = vec4[ 3 ];
+ break;
+
+ case F_INT:
+ *(int *)( b + f->ofs ) = atoi( value );
+ break;
+
+ case F_FLOAT:
+ *(float *)( b + f->ofs ) = atof( value );
+ break;
+
+ case F_ANGLEHACK:
+ v = atof( value );
+ ( (float *)( b + f->ofs ) )[ 0 ] = 0;
+ ( (float *)( b + f->ofs ) )[ 1 ] = v;
+ ( (float *)( b + f->ofs ) )[ 2 ] = 0;
+ break;
+
+ default:
+ case F_IGNORE:
+ break;
+ }
+
+ return;
+ }
+ }
+}
+
+
+
+
+/*
+===================
+G_SpawnGEntityFromSpawnVars
+
+Spawn an entity and fill in all of the level fields from
+level.spawnVars[], then call the class specfic spawn function
+===================
+*/
+void G_SpawnGEntityFromSpawnVars( void )
+{
+ int i;
+ gentity_t *ent;
+
+ // get the next free entity
+ ent = G_Spawn( );
+
+ for( i = 0 ; i < level.numSpawnVars ; i++ )
+ G_ParseField( level.spawnVars[ i ][ 0 ], level.spawnVars[ i ][ 1 ], ent );
+
+ G_SpawnInt( "notq3a", "0", &i );
+
+ if( i )
+ {
+ G_FreeEntity( ent );
+ return;
+ }
+
+ // move editor origin to pos
+ VectorCopy( ent->s.origin, ent->s.pos.trBase );
+ VectorCopy( ent->s.origin, ent->r.currentOrigin );
+
+ // if we didn't get a classname, don't bother spawning anything
+ if( !G_CallSpawn( ent ) )
+ G_FreeEntity( ent );
+}
+
+
+
+/*
+====================
+G_AddSpawnVarToken
+====================
+*/
+char *G_AddSpawnVarToken( const char *string )
+{
+ int l;
+ char *dest;
+
+ l = strlen( string );
+ if( level.numSpawnVarChars + l + 1 > MAX_SPAWN_VARS_CHARS )
+ G_Error( "G_AddSpawnVarToken: MAX_SPAWN_CHARS" );
+
+ dest = level.spawnVarChars + level.numSpawnVarChars;
+ memcpy( dest, string, l + 1 );
+
+ level.numSpawnVarChars += l + 1;
+
+ return dest;
+}
+
+/*
+====================
+G_ParseSpawnVars
+
+Parses a brace bounded set of key / value pairs out of the
+level's entity strings into level.spawnVars[]
+
+This does not actually spawn an entity.
+====================
+*/
+qboolean G_ParseSpawnVars( void )
+{
+ char keyname[ MAX_TOKEN_CHARS ];
+ char com_token[ MAX_TOKEN_CHARS ];
+
+ level.numSpawnVars = 0;
+ level.numSpawnVarChars = 0;
+
+ // parse the opening brace
+ if( !trap_GetEntityToken( com_token, sizeof( com_token ) ) )
+ {
+ // end of spawn string
+ return qfalse;
+ }
+
+ if( com_token[ 0 ] != '{' )
+ G_Error( "G_ParseSpawnVars: found %s when expecting {", com_token );
+
+ // go through all the key / value pairs
+ while( 1 )
+ {
+ // parse key
+ if( !trap_GetEntityToken( keyname, sizeof( keyname ) ) )
+ G_Error( "G_ParseSpawnVars: EOF without closing brace" );
+
+ if( keyname[0] == '}' )
+ break;
+
+ // parse value
+ if( !trap_GetEntityToken( com_token, sizeof( com_token ) ) )
+ G_Error( "G_ParseSpawnVars: EOF without closing brace" );
+
+ if( com_token[0] == '}' )
+ G_Error( "G_ParseSpawnVars: closing brace without data" );
+
+ if( level.numSpawnVars == MAX_SPAWN_VARS )
+ G_Error( "G_ParseSpawnVars: MAX_SPAWN_VARS" );
+
+ level.spawnVars[ level.numSpawnVars ][ 0 ] = G_AddSpawnVarToken( keyname );
+ level.spawnVars[ level.numSpawnVars ][ 1 ] = G_AddSpawnVarToken( com_token );
+ level.numSpawnVars++;
+ }
+
+ return qtrue;
+}
+
+
+
+/*QUAKED worldspawn (0 0 0) ?
+
+Every map should have exactly one worldspawn.
+"music" music wav file
+"gravity" 800 is default gravity
+"message" Text to print during connection process
+*/
+void SP_worldspawn( void )
+{
+ char *s;
+
+ G_SpawnString( "classname", "", &s );
+
+ if( Q_stricmp( s, "worldspawn" ) )
+ G_Error( "SP_worldspawn: The first entity isn't 'worldspawn'" );
+
+ // make some data visible to connecting client
+ trap_SetConfigstring( CS_GAME_VERSION, GAME_VERSION );
+
+ trap_SetConfigstring( CS_LEVEL_START_TIME, va( "%i", level.startTime ) );
+
+ G_SpawnString( "music", "", &s );
+ trap_SetConfigstring( CS_MUSIC, s );
+
+ G_SpawnString( "message", "", &s );
+ trap_SetConfigstring( CS_MESSAGE, s ); // map specific message
+
+ trap_SetConfigstring( CS_MOTD, g_motd.string ); // message of the day
+
+ G_SpawnString( "gravity", "800", &s );
+ trap_Cvar_Set( "g_gravity", s );
+
+ G_SpawnString( "humanBuildPoints", DEFAULT_HUMAN_BUILDPOINTS, &s );
+ trap_Cvar_Set( "g_humanBuildPoints", s );
+
+ G_SpawnString( "humanMaxStage", DEFAULT_HUMAN_MAX_STAGE, &s );
+ trap_Cvar_Set( "g_humanMaxStage", s );
+
+ G_SpawnString( "humanStage2Threshold", DEFAULT_HUMAN_STAGE2_THRESH, &s );
+ trap_Cvar_Set( "g_humanStage2Threshold", s );
+
+ G_SpawnString( "humanStage3Threshold", DEFAULT_HUMAN_STAGE3_THRESH, &s );
+ trap_Cvar_Set( "g_humanStage3Threshold", s );
+
+ G_SpawnString( "alienBuildPoints", DEFAULT_ALIEN_BUILDPOINTS, &s );
+ trap_Cvar_Set( "g_alienBuildPoints", s );
+
+ G_SpawnString( "alienMaxStage", DEFAULT_ALIEN_MAX_STAGE, &s );
+ trap_Cvar_Set( "g_alienMaxStage", s );
+
+ G_SpawnString( "alienStage2Threshold", DEFAULT_ALIEN_STAGE2_THRESH, &s );
+ trap_Cvar_Set( "g_alienStage2Threshold", s );
+
+ G_SpawnString( "alienStage3Threshold", DEFAULT_ALIEN_STAGE3_THRESH, &s );
+ trap_Cvar_Set( "g_alienStage3Threshold", s );
+
+ G_SpawnString( "enableDust", "0", &s );
+ trap_Cvar_Set( "g_enableDust", s );
+
+ G_SpawnString( "enableBreath", "0", &s );
+ trap_Cvar_Set( "g_enableBreath", s );
+
+ G_SpawnString( "disabledEquipment", "", &s );
+ trap_Cvar_Set( "g_disabledEquipment", s );
+
+ G_SpawnString( "disabledClasses", "", &s );
+ trap_Cvar_Set( "g_disabledClasses", s );
+
+ G_SpawnString( "disabledBuildables", "", &s );
+ trap_Cvar_Set( "g_disabledBuildables", s );
+
+ g_entities[ ENTITYNUM_WORLD ].s.number = ENTITYNUM_WORLD;
+ g_entities[ ENTITYNUM_WORLD ].classname = "worldspawn";
+
+ if( g_restarted.integer )
+ trap_Cvar_Set( "g_restarted", "0" );
+
+}
+
+
+/*
+==============
+G_SpawnEntitiesFromString
+
+Parses textual entity definitions out of an entstring and spawns gentities.
+==============
+*/
+void G_SpawnEntitiesFromString( void )
+{
+ // allow calls to G_Spawn*()
+ level.spawning = qtrue;
+ level.numSpawnVars = 0;
+
+ // the worldspawn is not an actual entity, but it still
+ // has a "spawn" function to perform any global setup
+ // needed by a level (setting configstrings or cvars, etc)
+ if( !G_ParseSpawnVars( ) )
+ G_Error( "SpawnEntities: no entities" );
+
+ SP_worldspawn( );
+
+ // parse ents
+ while( G_ParseSpawnVars( ) )
+ G_SpawnGEntityFromSpawnVars( );
+
+ level.spawning = qfalse; // any future calls to G_Spawn*() will be errors
+} \ No newline at end of file