]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blobdiff - qcsrc/server/mutators/gamemode_keepaway.qc
Merge remote branch 'origin/master' into samual/keepaway
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / mutators / gamemode_keepaway.qc
index eb1bc4bb002511795d5ccd85dca21c1e1f1242a3..04939e8398fe42dc06e7bdc98ed41c347c76279e 100644 (file)
@@ -1,35 +1,62 @@
-void ka_Initialize()
+void ka_SpawnBall(void);
+void ka_TouchEvent(void);
+void ka_RespawnBall(void);
+void ka_DropEvent(entity);
+
+float ka_ballcarrier_waypointsprite_visible_for_player(entity);
+
+void ka_Initialize() // run at the start of a match, initiates game mode
 {
-       if(!g_keepaway) { 
-               remove(self); 
-               return; 
-       }
-       if (!self.model) {
-               self.model = "models/nexball/ball.md3"; 
-               self.scale = 1.3;
-       }
+       if(!g_keepaway)
+               return;
+               
+       precache_sound("keepaway/pickedup.wav");
+       precache_sound("keepaway/dropped.wav");
+       precache_sound("keepaway/respawn.wav");
+       precache_sound("keepaway/touch.wav");
+
+       ScoreRules_keepaway();
+       ka_SpawnBall();
+}
+
+void ka_Reset() // used to clear the ballcarrier whenever the match switches from warmup to normal
+{
+       if(self.owner)
+               if(self.owner.classname == "player")
+                       ka_DropEvent(self.owner);
 
-       precache_model(self.model);
-       setmodel(self, self.model);
-       setsize(self, BALL_MINS, BALL_MAXS);
-       ball_scale = self.scale;
-       self.classname = "keepawayball";
-       self.damageforcescale = cvar("g_keepawayball_damageforcescale");
-       self.effects = self.effects | EF_FULLBRIGHT;
-       self.movetype = MOVETYPE_BOUNCE;
-       self.touch = ka_TouchEvent;
-       self.think = ka_SpawnBall;
-       self.nextthink = time;
-       self.flags = FL_ITEM;
-       self.reset = ka_Reset;
-       self.owner = world;
+       ka_RespawnBall();
+}
+
+void ka_SpawnBall() // loads various values for the ball, runs only once at start of match
+{
+       if(!g_keepaway) { return; }
        
-       // todo: Waypoints and radar
-       //WaypointSprite_AttachCarrier();
+       entity e;
+       e = spawn();
+       e.model = "models/orbs/orbblue.md3";    
+       e.scale = 1;
+       precache_model(e.model);
+       setmodel(e, e.model);
+       setsize(e, '-16 -16 -20', '16 16 20'); // 20 20 20 was too big, player is only 16 16 24... gotta cheat with the Z (20) axis so that the particle isn't cut off
+       e.classname = "keepawayball";
+       e.damageforcescale = autocvar_g_keepawayball_damageforcescale;
+       e.takedamage = DAMAGE_YES;
+       e.glow_color = autocvar_g_keepawayball_trail_color;
+       e.glow_trail = TRUE;
+       e.movetype = MOVETYPE_BOUNCE;
+       e.touch = ka_TouchEvent;
+       e.flags = FL_ITEM;
+       e.reset = ka_Reset;
+       e.owner = world;
+
+       InitializeEntity(e, ka_RespawnBall, INITPRIO_SETLOCATION); // is this the right priority? Neh, I have no idea.. Well-- it works! So. 
 }
 
-void ka_SpawnBall()
+void ka_RespawnBall() // runs whenever the ball needs to be relocated
 {
+       vector oldballorigin = self.origin;
+
        if(MoveToRandomMapLocation(self, DPCONTENTS_SOLID | DPCONTENTS_CORPSE | DPCONTENTS_PLAYERCLIP, DPCONTENTS_SLIME | DPCONTENTS_LAVA | DPCONTENTS_SKY | DPCONTENTS_BODY | DPCONTENTS_DONOTENTER, Q3SURFACEFLAG_SKY, 10, 1024, 256))
        {
                makevectors(self.angles);
@@ -37,249 +64,285 @@ void ka_SpawnBall()
                self.velocity = '0 0 200';
                self.angles = '0 0 0';
                self.solid = SOLID_TRIGGER;
-               //self.touch = ka_TouchEvent;
-               self.think = ka_SpawnBall;
-               self.nextthink = time + cvar("g_keepawayball_respawntime");
+               self.think = ka_RespawnBall;
+               self.nextthink = time + autocvar_g_keepawayball_respawntime;
+               
+               pointparticles(particleeffectnum("electro_combo"), oldballorigin, '0 0 0', 1);
+               pointparticles(particleeffectnum("electro_combo"), self.origin, '0 0 0', 1);
+
+               WaypointSprite_Spawn("ka-ball", 0, 0, self, '0 0 64', world, self.team, self, waypointsprite_attachedforcarrier, FALSE);
+               WaypointSprite_UpdateTeamRadar(self.waypointsprite_attachedforcarrier, RADARICON_FLAGCARRIER, '0 1 1');
+               WaypointSprite_Ping(self.waypointsprite_attachedforcarrier);    
+
+               sound(self, CHAN_AUTO, "keepaway/respawn.wav", VOL_BASE, ATTN_NONE); // ATTN_NONE (it's a sound intended to be heard anywhere) 
        }
        else
        {
-               // sorry, can't spawn, better luck next frame
-               self.think = ka_SpawnBall;
-               self.nextthink = time;
+               ka_RespawnBall(); // finding a location failed, retry 
        }
 }
 
-void ka_TouchEvent(entity plyr)
+void ka_TouchEvent() // runs any time that the ball comes in contact with something
 {
+       if(!self) { return; }
        if(trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT)
-       {
-               self.think = ka_SpawnBall;
-               self.nextthink = time;
+       { // The ball fell off the map, respawn it since players can't get to it
+               ka_RespawnBall();
                return;
        }
-       if (!plyr) 
-               return;
-       if (!self) 
-               return;
-       if ((other.classname != "player" || other.health < 1) && (time > self.ctf_droptime + cvar("g_keepawayball_respawntime")))
-               return;
-       if (self.wait > time)
-               return;
+       if(other.deadflag != DEAD_NO) { return; }
+       if(other.classname != "player") 
+       {  // The ball just touched an object, most likely the world
+               pointparticles(particleeffectnum("kaball_sparks"), self.origin, '0 0 0', 1);
+               sound(self, CHAN_AUTO, "keepaway/touch.wav", VOL_BASE, ATTN_NORM);
+               return; 
+       }
+       else if(self.wait > time) { return; }
 
+       // attach the ball to the player
        self.owner = other;
        other.ballcarried = self;
        setattachment(self, other, "");
-       setorigin(self, BALL_ATTACHORG);
+       setorigin(self, '3 0 20'); // wtf why is this not '0 0 0' ? 
        
+       // make the ball invisible/unable to do anything
        self.velocity = '0 0 0';
        self.movetype = MOVETYPE_NONE;
        self.touch = SUB_Null;
-       self.alpha = 0.01;
-       
+       self.effects |= EF_NODRAW;
        self.think = SUB_Null;
        self.nextthink = 0;
+       self.takedamage = DAMAGE_NO;
 
-       self.glow_color = cvar("g_keepawayball_trail_color");
-       self.glowtrail = TRUE;
-       plyr.effects |= 8;
-       plyr.alpha = 0.6
+       // apply effects to player
+       other.glow_color = autocvar_g_keepawayball_trail_color;
+       other.glow_trail = TRUE;
+       other.effects |= EF_DIMLIGHT;
+       other.alpha = autocvar_g_keepaway_ballcarrier_alpha;
+       other.exteriorweaponentity.alpha = autocvar_g_keepaway_ballcarrier_alpha;
 
-       bprint(other.netname, "^7 has picked up the ball!\n");
+       // messages and sounds
+       Send_KillNotification(other.netname, "", "", KA_PICKUPBALL, MSG_KA);
        WriteByte(MSG_BROADCAST, SVC_CENTERPRINT);
        WriteString(MSG_BROADCAST, strcat("\n\n", other.netname, "^7 has picked up the ball!\n"));
-       sound(self.owner, CHAN_AUTO, "keepaway/pickedup.wav", VOL_BASE, ATTN_NORM);
+       sound(self.owner, CHAN_AUTO, "keepaway/pickedup.wav", VOL_BASE, ATTN_NONE); // ATTN_NONE (it's a sound intended to be heard anywhere) 
        
+       // scoring
        PlayerScore_Add(other, SP_KEEPAWAY_PICKUPS, 1);
 
-       // todo: Waypoints and radar
+       // waypoints
+       WaypointSprite_AttachCarrier("ka-ballcarrier", other);
+       other.waypointsprite_attachedforcarrier.waypointsprite_visible_for_player = ka_ballcarrier_waypointsprite_visible_for_player;
+       WaypointSprite_UpdateRule(other.waypointsprite_attachedforcarrier, 0, SPRITERULE_DEFAULT);
+       WaypointSprite_UpdateTeamRadar(other.waypointsprite_attachedforcarrier, RADARICON_FLAGCARRIER, '1 0 0');
+       WaypointSprite_Ping(other.waypointsprite_attachedforcarrier);   
+       WaypointSprite_Kill(self.waypointsprite_attachedforcarrier);
 }
 
-void ka_DropEvent(entity plyr, entity ball)
-{      
+void ka_DropEvent(entity plyr) // runs any time that a player is supposed to lose the ball
+{
+       entity ball;
+       ball = plyr.ballcarried;
+
+       if(!ball) { return; }
+       
+       // reset the ball
        setattachment(ball, world, "");
        ball.movetype = MOVETYPE_BOUNCE;
-       ball.solid = SOLID_TRIGGER;
-       ball.wait = time + 1;
-       ball.ctf_droptime = time;
-       ball.think = ka_SpawnBall;
-       ball.nextthink = time + cvar("g_keepawayball_respawntime");
+       ball.solid = SOLID_TRIGGER; // is this needed? 
+       ball.wait = time + 1; 
+       ball.think = ka_RespawnBall;
+       ball.nextthink = time + autocvar_g_keepawayball_respawntime;
        ball.touch = ka_TouchEvent;
-       plyr.effects = EF_LOWPRECISION;
-       plyr.alpha = 1.0;
-       ball.alpha = 1.0;
+       ball.takedamage = DAMAGE_YES;
+       ball.effects &~= EF_NODRAW; 
        setorigin(ball, plyr.origin + '0 0 10');
        ball.velocity = '0 0 200' + '0 100 0'*crandom() + '100 0 0'*crandom();
+       ball.owner.ballcarried = world; // I hope nothing checks to see if the world has the ball in the rest of my code :P 
+       ball.owner = world;
+       
+       // reset the player effects
+       plyr.glow_trail = FALSE;
+       plyr.effects &~= EF_DIMLIGHT;
+       plyr.alpha = default_player_alpha;
+       plyr.exteriorweaponentity.alpha = default_weapon_alpha; 
        
-       bprint(plyr.netname, "^7 has dropped the ball!\n");
+       // messages and sounds
+       Send_KillNotification(plyr.netname, "", "", KA_DROPBALL, MSG_KA);
        WriteByte(MSG_BROADCAST, SVC_CENTERPRINT);
        WriteString(MSG_BROADCAST, strcat("\n\n", plyr.netname, "^7 has dropped the ball!\n"));
-       sound(other, CHAN_AUTO, "keepaway/dropped.wav", VOL_BASE, ATTN_NORM);   
+       sound(other, CHAN_AUTO, "keepaway/dropped.wav", VOL_BASE, ATTN_NONE);   // ATTN_NONE (it's a sound intended to be heard anywhere) 
        
+       // scoring
        PlayerScore_Add(plyr, SP_KEEPAWAY_DROPS, 1);
        
-       // todo
-       //WaypointSprite_AttachCarrier("ka-ball", ball);
-       //WaypointSprite_Kill(plyr.waypointsprite_attachedforcarrier);
-       
-       ball.owner.kaballcarried = world;
-       ball.owner = world;
+       // waypoints
+       WaypointSprite_Spawn("ka-ball", 0, 0, ball, '0 0 64', world, ball.team, ball, waypointsprite_attachedforcarrier, FALSE);
+       WaypointSprite_UpdateRule(ball.waypointsprite_attachedforcarrier, 0, SPRITERULE_DEFAULT);
+       WaypointSprite_UpdateTeamRadar(ball.waypointsprite_attachedforcarrier, RADARICON_FLAGCARRIER, '0 1 1');
+       WaypointSprite_Ping(ball.waypointsprite_attachedforcarrier);    
+       WaypointSprite_Kill(plyr.waypointsprite_attachedforcarrier);
 }
 
-/*
-void ka_CheckWinner()
+float ka_ballcarrier_waypointsprite_visible_for_player(entity e) // runs on waypoints which are attached to ballcarriers, updates once per frame 
 {
+       if(e.ballcarried)
+               if(other.classname == "spectator") 
+                       return FALSE; // we don't want spectators of the ballcarrier to see the attached waypoint on the top of their screen
+               
+       // TODO: Make the ballcarrier lack a waypointsprite whenever they have the invisibility powerup
 
+       return TRUE;
 }
 
-MUTATOR_HOOKFUNCTION(ka_PlayerDies)
+MUTATOR_HOOKFUNCTION(ka_RemovePlayer)
 {
-       float i;
-       entity e;
-
-       float temp_tag_players_count;
-       temp_tag_players_count = tag_players_count;
+       if(self.ballcarried) { ka_DropEvent(self); } // a player with the ball has left the match, drop it
+       return 0;
+}
 
-       if(frag_target.tagcolor == frag_target.tagcolor_original) // if this is the first time we die... (our tagcolor remained unchanged)
+MUTATOR_HOOKFUNCTION(ka_Scoring)
+{
+       if((frag_attacker != frag_target) && (frag_attacker.classname == "player"))
        {
-               for(i = 0; i < temp_tag_players_count; ++i) // check other players...
-               {
-                       e = tag_players[i];
-                       if(e == world) // empty slot, skip to next
-                       {
-                               if(temp_tag_players_count < TAGCOLOR_MAX - 1) // just in case
-                                       ++temp_tag_players_count;
-                               continue;
-                       }
-
-                       if(e.tagcolor == frag_target.tagcolor_original) // and see if they have our original tag color
-                       {
-                               tag_GetFragAttackers_ColorOwner();
-                               centerprint(e, strcat("^1Your master ^7", frag_target.netname, "^1 was tagged by ^7", frag_attacker.netname, " ^1with ^7", color_owner_red, " ^1color.\n"));
-                               e.tagcolor = frag_attacker.tagcolor; // if so, remove it, our tag color has now "died out" from this round and we can not win anymore. The attacker will "summon" all of our previously fragged targets, and also us.
-                               setcolor(e, 16 * e.tagcolor + e.tagcolor);
-                       }
+               if(frag_target.ballcarried) { // add to amount of times killing carrier
+                       PlayerScore_Add(frag_attacker, SP_KEEPAWAY_CARRIERKILLS, 1);
+                       if(autocvar_g_keepaway_bckillscore) // add bckills to the score
+                               PlayerScore_Add(frag_attacker, SP_KEEPAWAY_SCORE, 1);
                }
-       }
-       else
-       {
-               frag_target.tagcolor = frag_attacker.tagcolor;
-               setcolor(frag_target, 16 * frag_target.tagcolor + frag_target.tagcolor);
-       }
+               else if(!frag_attacker.ballcarried)
+                       if(autocvar_g_keepaway_noncarrier_warn)
+                               centerprint_atprio(frag_attacker, (CENTERPRIO_SPAM + 5), "Killing people while you don't have the ball gives no points!");
 
-       tag_GetFragAttackers_ColorOwner();
+               if(frag_attacker.ballcarried) // add to amount of kills while ballcarrier
+                       PlayerScore_Add(frag_attacker, SP_KEEPAWAY_SCORE, 1);
+       }
 
-       if(color_owner_self)
-               color_owner_green = "^2your own";
-       centerprint(frag_attacker, strcat("^2You tagged ^7", frag_target.netname, " ^2with ^7", color_owner_green, " ^2color.\n"));
+       if(self.ballcarried) { ka_DropEvent(self); } // a player with the ball has died, drop it
+       return 0;
+}
 
-       if(color_owner_self)
-               color_owner_red = "^1their own";
-       centerprint(frag_target, strcat("^1You were tagged by ^7", frag_attacker.netname, " ^1with ^7", color_owner_red, " ^1color.\n"));
-       bprint("^7", frag_target.netname, "^1 was tagged by ^7", frag_attacker.netname, " ^1with ^7", color_owner_red, " ^1color.\n");
+MUTATOR_HOOKFUNCTION(ka_GiveFragsForKill)
+{
+       frag_score = 0; // no frags counted in keepaway
+       return 0;
+}
 
-       frag_target.health = cvar("g_balance_health_start"); // "respawn" the player :P
+MUTATOR_HOOKFUNCTION(ka_PlayerPreThink)
+{
+       // clear the item used for the ball in keepaway
+       self.items &~= IT_KEY1;
+       
+       // if the player has the ball, make sure they have the item for it (Used for HUD primarily)
+       if(self.ballcarried)
+               self.items |= IT_KEY1;
 
-       tag_CheckWinner();
+       // drop the ball if the player presses the use button
+       if(self.BUTTON_USE)
+               if(self.ballcarried) { ka_DropEvent(self); } 
 
-       return 1;
+       return 0;
 }
 
-MUTATOR_HOOKFUNCTION(tag_RemovePlayer)
+MUTATOR_HOOKFUNCTION(ka_PlayerDamage) // for changing damage and force values that are applied to players in g_damage.qc
 {
-       if(self.tag_playernum == -1)
-               return 0; // nothing to remove
-
-       float i, j;
-       /*for (i = self.tag_playernum; i < tag_players_count; ++i)
+       if(frag_attacker.ballcarried) // if the attacker is a ballcarrier
        {
-               tag_players[i] = tag_players[i+1];
-               tag_players[i].tag_playernum = tag_players[i].tag_playernum - 1;
+               if(frag_target == frag_attacker) // damage done to yourself
+               {
+                       frag_damage *= autocvar_g_keepaway_ballcarrier_selfdamage;
+                       frag_force *= autocvar_g_keepaway_ballcarrier_selfforce;
+               }
+               else // damage done to noncarriers
+               {
+                       frag_damage *= autocvar_g_keepaway_ballcarrier_damage;
+                       frag_force *= autocvar_g_keepaway_ballcarrier_force;
+               }
        }
-       */
-
-       tag_players[self.tag_playernum] = world;
-       tag_players_count = tag_players_count - 1;
-
-       // if other players have our color, randomize their color
-       entity e, random_player;
-       float temp_tag_players_count;
-       temp_tag_players_count = tag_players_count;
-
-       if(!next_round) // ... but ONLY if next_round isn't set. We don't care about the colors if the round has already ended
-       for(i = 0; i < temp_tag_players_count; ++i) // check other players...
+       else if not(frag_target.ballcarried) // if the target is a noncarrier
        {
-               e = tag_players[i];
-               if(e == world) // empty slot, skip to next
+               if(frag_target == frag_attacker) // damage done to yourself
                {
-                       if(temp_tag_players_count < TAGCOLOR_MAX - 1) // just in case
-                               ++temp_tag_players_count;
-                       continue;
+                       frag_damage *= autocvar_g_keepaway_noncarrier_selfdamage;
+                       frag_force *= autocvar_g_keepaway_noncarrier_selfforce;
                }
-
-               if(e.tagcolor == self.tagcolor_original) // and see if they have our original tag color
+               else // damage done to other noncarriers
                {
-                       for(j = 0; j < 100; ++j) // try 100 times to find a color that isn't the same as our color. If this fails we are either damn unlucky, or there are really only players left of our color
-                       {
-                               random_player = tag_players[floor(random() * (TAGCOLOR_MAX - 1))];
-
-                               if(random_player == world) // hit empty slot, try again
-                                       continue;
-
-                               if(random_player.tagcolor != self.tagcolor_original) // break if we found another color
-                               {
-                                       break;
-                               }
-                       }
-                       e.tagcolor = random_player.tagcolor;
-                       setcolor(e, 16 * e.tagcolor + e.tagcolor);
+                       frag_damage *= autocvar_g_keepaway_noncarrier_damage;
+                       frag_force *= autocvar_g_keepaway_noncarrier_force;
                }
        }
-
-       self.tag_playernum = -1;
-
-       if(tag_players_count > 1 && time > warmup)
-               tag_CheckWinner();
-
-       return 1;
-}
-
-MUTATOR_HOOKFUNCTION(tag_GiveFragsForKill)
-{
-       frag_score = 0; // no frags counted in Tag, maybe later (TODO)
-       return 1;
+       return 0;
 }
 
-MUTATOR_HOOKFUNCTION(tag_PlayerPreThink)
+MUTATOR_HOOKFUNCTION(ka_PlayerPowerups)
 {
-       setcolor(self, 16 * self.tagcolor + self.tagcolor); // prevent cheating by changing player colors
-       return 1;
+       // right now this hook doesn't make much sense (It's actually useless this way except for minstagib invisibility alpha) 
+       // but in the future it's supposed to allow me to do some extra stuff with waypointsprites and invisibility powerup
+       // So bare with me until I can fix a certain bug with ka_ballcarrier_waypointsprite_visible_for_player() 
+       
+       // also note that this structure makes no sense (Rather there is a better way to do it) the way it's currently applied
+       // again just bare with me as this is for a future feature. 
+
+       if(self.ballcarried)
+       { 
+               // force the default ballcarrier alpha on the player if they have the ball
+               self.alpha = autocvar_g_keepaway_ballcarrier_alpha;
+               self.exteriorweaponentity.alpha = autocvar_g_keepaway_ballcarrier_alpha;
+       
+               if(g_minstagib)
+               {
+                       if(olditems & IT_STRENGTH) 
+                       { // if the player has the ball and they also have the invisibility powerup, apply alpha accordingly
+                               self.alpha = g_minstagib_invis_alpha;
+                               self.exteriorweaponentity.alpha = g_minstagib_invis_alpha;
+                       }
+               }
+       }
+       else if(g_minstagib)
+       {
+               // if we're in minstagib and a noncarrier has invisibility, assure that we apply the invisibility effects normally
+               if(olditems & IT_STRENGTH) 
+               {
+                       self.alpha = g_minstagib_invis_alpha;
+                       self.exteriorweaponentity.alpha = g_minstagib_invis_alpha;
+               }
+       }
+       else
+       {
+               // if we're a normal player with no powerups that edit alpha make sure the alpha is default. 
+               // (normal powerups just use EF_ADDITIVE)
+               self.alpha = default_player_alpha;
+               self.exteriorweaponentity.alpha = default_weapon_alpha;
+       }
+       
+       return 0;
 }
-*/
 
-
-MUTATOR_DEFINITION(gamemode_ka)
+MUTATOR_DEFINITION(gamemode_keepaway)
 {
+       // I don't quite understand these orders, perhaps someone could enlighten me?
        MUTATOR_HOOK(MakePlayerObserver, ka_RemovePlayer, CBC_ORDER_ANY);
        MUTATOR_HOOK(ClientDisconnect, ka_RemovePlayer, CBC_ORDER_ANY);
-       MUTATOR_HOOK(PlayerDies, ka_PlayerDies, CBC_ORDER_ANY);
-       //MUTATOR_HOOK(PlayerSpawn, ka_PlayerSpawn, CBC_ORDER_ANY);
-       //MUTATOR_HOOK(GiveFragsForKill, ka_GiveFragsForKill, CBC_ORDER_FIRST);
-       //MUTATOR_HOOK(PlayerPreThink, ka_PlayerPreThink, CBC_ORDER_FIRST);
+       MUTATOR_HOOK(PlayerDies, ka_Scoring, CBC_ORDER_ANY);
+       MUTATOR_HOOK(GiveFragsForKill, ka_GiveFragsForKill, CBC_ORDER_ANY);
+       MUTATOR_HOOK(PlayerPreThink, ka_PlayerPreThink, CBC_ORDER_FIRST);
+       MUTATOR_HOOK(PlayerDamage_Calculate, ka_PlayerDamage, CBC_ORDER_ANY);
+       MUTATOR_HOOK(PlayerPowerups, ka_PlayerPowerups, CBC_ORDER_ANY);
 
        MUTATOR_ONADD
        {
                if(time > 1) // game loads at time 1
                        error("This is a game type and it cannot be added at runtime.");
-               g_ka = 1;
+               g_keepaway = 1;
                ka_Initialize();
        }
 
        MUTATOR_ONREMOVE
        {
-               g_ka = 0;
+               g_keepaway = 0;
                error("This is a game type and it cannot be removed at runtime.");
        }
 
        return 0;
 }
-