This is Unit.m in view mode; [Download] [Up]
// // $Id: Unit.m,v 1.9 1997/10/31 04:52:11 nygard Exp $ // // // This file is a part of Empire, a game of exploration and conquest. // Copyright (C) 1996 Steve Nygard // // This program 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. // // This program 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 this program; if not, write to the Free Software // Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. // // You may contact the author by: // e-mail: nygard@telusplanet.net // #import "Empire.h" RCSID ("$Id: Unit.m,v 1.9 1997/10/31 04:52:11 nygard Exp $"); #import "Unit.h" #import "Map.h" #import "City.h" #import "EmpireImageVendor.h" #import "EmPlayer.h" #import "Orders.subproj/Orders.h" #import "GameManager.h" // For berserk //---------------------------------------------------------------------- // hit points - damage sustained before unit is destroyed. // speed - number of moves per turn the unit can take. An attack counts // as a move. // fuel size - fighters have a limited fuel capacity and must be // refueled in cities or carriers. // cargo capacity - number of units this type can hold, or -1 if it // can't carry other units. Note: There are further // restrictions on what types can be carried. // damage per hit - damage to enemy for each hit landed. // moves on land - can this unit move on (or over) land? // moves on water - can this unit move on (or over) water? // attack factor - adjusts the probability or a successful attack based // on the unit type. // defense factor - adjusts the probability or a successful defense // based on the unit type. // defense factor - adjusts the probability or a successful defense // against bombardment, based on the unit type. //---------------------------------------------------------------------- typedef struct { int hitPoints; int speed; // per turn int fuel_size; int cargo_capacity; int damage_per_hit; BOOL moves_on_land; BOOL moves_on_water; float attack_factor; float defense_factor; float defense_factor_against_bombardment; } UnitTypeAttributes; static UnitTypeAttributes unitTypeAttributes[UNIT_TYPE_COUNT] = { { 0, 0, -1, 0, 0, NO, NO , 0, 0, 0 }, // u_unknown { 1, 1, -1, 0, 1, YES, NO , 1, 1, 0.5 }, // u_army { 1, 5, 20, 0, 1, YES, YES, 1, 1, 1 }, // u_fighter { 3, 2, -1, 6, 1, NO, YES, 0.5, 0.5, 0.5 }, // u_transport { 2, 2, -1, 0, 3, NO, YES, 1, 0.5, 0.5 }, // u_submarine { 3, 3, -1, 0, 1, NO, YES, 1, 1, 1 }, // u_destroyer { 8, 2, -1, 0, 2, NO, YES, 1, 1, 1 }, // u_cruiser { 8, 2, -1, 8, 1, NO, YES, 0.5, 1, 1 }, // u_carrier { 12, 2, -1, 0, 3, NO, YES, 1, 1, 1 }, // u_battleship { 1, 5, -1, 0, 1, YES, YES, 0, 0, 0 }, // u_hovercraft }; //---------------------------------------------------------------------- // This simply keeps track of the number of units of each type produced, // so that we can give them unique names to each unit. (i.e. Army #7). // // Ultimately, there should be a more intelligent name assignment. For // example, use lists of names from a text file for ships, and rules // for armies and fighters. (i.e. 57th Army, 72nd Fighter). //---------------------------------------------------------------------- static int _typeCount[UNIT_TYPE_COUNT]; //====================================================================== // Most of the attributes of a unit can be parameterized, so there is // no need for separate subclasses for each type of unit. //====================================================================== #define Unit_VERSION 1 @implementation Unit + (void) initialize { if (self == [Unit class]) { [self setVersion:Unit_VERSION]; } } //---------------------------------------------------------------------- - initWithUnitType:(UnitType)aUnitType inCity:(City *)producingCity { [super init]; NSAssert (aUnitType >= u_army && aUnitType <= u_hovercraft, @"Invalid unit type."); unitType = aUnitType; // We don't "own" the owner, so it is not retained. owner = [producingCity owner]; unitName = [[NSString stringWithFormat:@"%@ #%d", EMUnitTypeName (unitType), ++_typeCount[unitType]] retain]; unitLocation = [producingCity cityLocation]; currentHitPoints = unitTypeAttributes[unitType].hitPoints; currentRange = unitTypeAttributes[unitType].speed; fuel = unitTypeAttributes[unitType].fuel_size; order = nil; // Hmm. With more than one game manager, how is the Berserk On/Off menu item handled? if ([[owner gameManager] isBerserk] == YES) { order = [[OExplore alloc] initForUnit:self explore:[owner map]]; } isDestroyed = NO; isDoneTurn = NO; if (unitTypeAttributes[unitType].cargo_capacity > 0) cargo = [[NSMutableArray array] retain]; else cargo = nil; onBoard = nil; withinCity = producingCity; [producingCity unitDidEnter:self]; return self; } //---------------------------------------------------------------------- - (void) dealloc { // Need to notify anything that might be keeping a reference... if (withinCity != nil) [withinCity unitDidExit:self]; if (onBoard != nil) [onBoard unitDidExit:self]; SNRelease (unitName); SNRelease (order); SNRelease (cargo); [super dealloc]; } //---------------------------------------------------------------------- - (NSString *) description { return [NSString stringWithFormat:@"<%@>", [self statusLine]]; } //====================================================================== // General static values //====================================================================== - (UnitType) unitType { return unitType; } //---------------------------------------------------------------------- - (EmPlayer *) owner { return owner; } //---------------------------------------------------------------------- - (Player) playerNumber { return [owner playerNumber]; } //---------------------------------------------------------------------- - (NSString *) unitName { return unitName; } //---------------------------------------------------------------------- - (BOOL) isAShip { return unitType >= u_transport && unitType <= u_battleship; } //====================================================================== // Combat information //====================================================================== - (int) currentHitPoints { return currentHitPoints; } //---------------------------------------------------------------------- - (int) maxHitPoints { return unitTypeAttributes[unitType].hitPoints; } //---------------------------------------------------------------------- - (int) damagePerHit { return unitTypeAttributes[unitType].damage_per_hit; } //---------------------------------------------------------------------- - (float) attackFactor { return unitTypeAttributes[unitType].attack_factor; } //---------------------------------------------------------------------- - (float) defenseFactor:(BOOL)bombardment { if (bombardment == YES) return unitTypeAttributes[unitType].defense_factor; return unitTypeAttributes[unitType].defense_factor_against_bombardment; } //---------------------------------------------------------------------- - (BOOL) canBombard { return (unitType == u_cruiser) || (unitType == u_battleship); } //====================================================================== // Movement information //====================================================================== - (int) currentRange { return currentRange; } //---------------------------------------------------------------------- - (int) remainingFuel { return fuel; } //---------------------------------------------------------------------- - (BOOL) canMoveOnTerrain:(Terrain)terrain { BOOL canMove = YES; switch (terrain) { case t_land: canMove = unitTypeAttributes[unitType].moves_on_land; break; case t_water: canMove = unitTypeAttributes[unitType].moves_on_water; break; case t_city: canMove = NO; break; default: break; } return canMove; } //---------------------------------------------------------------------- - (MovementType) movementType { int movement = 0; if (unitTypeAttributes[unitType].moves_on_land == YES) movement |= LAND_UNIT; if (unitTypeAttributes[unitType].moves_on_water == YES) movement |= WATER_UNIT; return movement; } //---------------------------------------------------------------------- - (EMMapLocation) unitLocation { return unitLocation; } //---------------------------------------------------------------------- // Set the new location of a unit. This takes a look at the map to // determine if it's entering a city or boarding a ship. //---------------------------------------------------------------------- - (void) setLocation:(EMMapLocation)newLocation { MapToken mapToken = [[owner map] tokenAtLocation:newLocation]; Terrain terrain; Icon icon; UnitType targetUnitType; unitLocation = newLocation; // We need to update the cargo locations before we set the city. if (cargo != nil) { NSEnumerator *unitEnumerator = [cargo objectEnumerator]; Unit *unit; while (unit = [unitEnumerator nextObject]) { [unit moved]; [unit takeLocationFromTransport]; } } // Assuming that the player is us. EMMapTokenComponents (mapToken, &terrain, NULL, &icon, NULL); targetUnitType = EMConvertIconToUnitType (icon); if (terrain == t_city) { [self setCity:[owner cityAtLocation:newLocation]]; } else [self setCity:nil]; if ((unitType == u_army && targetUnitType == u_transport) || (unitType == u_fighter && targetUnitType == u_carrier) ) { // What happens if it doesn't fit? Or is it just assumed to fit by this point? [[owner unitWithType:targetUnitType atLocation:newLocation] loadUnit:self]; //[self setOnBoardShip:[owner unitWithType:targetUnitType atLocation:newLocation]]; } else { [self setOnBoardShip:nil]; } } //---------------------------------------------------------------------- // If a ship moves, it notifies all of its cargo so they can update // their location to match that of the ship. //---------------------------------------------------------------------- - (void) takeLocationFromTransport { if (onBoard != nil) unitLocation = [onBoard unitLocation]; } //====================================================================== // Display //====================================================================== - (Icon) icon { static const Icon baseIconsForUnitTypes[] = {i_none, i_army, i_fighter, i_unloaded_transport, i_submarine, i_destroyer, i_cruiser, i_unloaded_carrier, i_battleship, i_hovercraft}; Icon icon = baseIconsForUnitTypes[unitType]; if (unitType == u_army && [self isSentried] == YES) { icon = i_sentry; } else if (unitType == u_transport && cargo != nil && [cargo count] > 0) { icon = i_loaded_transport; } else if (unitType == u_carrier && cargo != nil && [cargo count] > 0) { icon = i_loaded_carrier; } return icon; } //---------------------------------------------------------------------- - (NSImage *) iconImage { NSImage *aCity, **onLand, **onWater, **onCity; EmpireImageVendor *vendor = [EmpireImageVendor instance]; NSImage *image = nil; [vendor player:[owner playerNumber]:&aCity:&onLand:&onWater:&onCity:NULL]; switch ([self icon]) { case i_army: image = onLand[0]; break; case i_sentry: image = onLand[1]; break; case i_fighter: image = onLand[2]; break; case i_unloaded_transport: image = onWater[1]; break; case i_loaded_transport: image = onWater[2]; break; case i_submarine: image = onWater[3]; break; case i_destroyer: image = onWater[4]; break; case i_cruiser: image = onWater[5]; break; case i_unloaded_carrier: image = onWater[6]; break; case i_loaded_carrier: image = onWater[7]; break; case i_battleship: image = onWater[8]; break; case i_hovercraft: image = onWater[9]; break; default: image = nil; } return image; } //====================================================================== // Order support //====================================================================== - (void) setOrder:(Order *)newOrder { BOOL s1, s2; s1 = s2 = NO; if (order != nil) s1 = [order isSentried]; if (newOrder != nil) s2 = [newOrder isSentried]; SNRelease (order); order = [newOrder retain]; if ((s1 == YES && s2 == NO) || (s1 == NO && s2 == YES)) { [owner unitIconHasChanged:self]; [withinCity unitIconHasChanged:self]; } } //---------------------------------------------------------------------- - (Order *) currentOrder { return order; } //---------------------------------------------------------------------- - (BOOL) isSentried { return (order != nil) && [order isSentried] == YES; } //---------------------------------------------------------------------- // Damaged ships have a reduced cargo capacity. //---------------------------------------------------------------------- - (int) remainingCargoCapacity { if (cargo == nil) { return 0; } else if (unitType == u_transport) { return 2 * currentHitPoints - [cargo count]; } else if (unitType == u_carrier) { return currentHitPoints - [cargo count]; } return unitTypeAttributes[unitType].cargo_capacity - [cargo count]; } //---------------------------------------------------------------------- - (BOOL) isFull { return [self remainingCargoCapacity] == 0; } //====================================================================== // Turn handling //====================================================================== //---------------------------------------------------------------------- // Only ships can repair (other units only have 1 hit point) //---------------------------------------------------------------------- - (void) repairDamage { if (currentHitPoints < unitTypeAttributes[unitType].hitPoints) currentHitPoints++; } //---------------------------------------------------------------------- // Only fighters need to refuel... But only in city/carrier. //---------------------------------------------------------------------- - (void) refuel { if (unitTypeAttributes[unitType].fuel_size > 0 && (withinCity != nil || onBoard != nil)) fuel = unitTypeAttributes[unitType].fuel_size; } //---------------------------------------------------------------------- - (void) resetRange { if (currentHitPoints <= unitTypeAttributes[unitType].hitPoints / 2) currentRange = unitTypeAttributes[unitType].speed / 2; else currentRange = unitTypeAttributes[unitType].speed; isDoneTurn = NO; } //---------------------------------------------------------------------- - (void) skipMove { isDoneTurn = YES; } //---------------------------------------------------------------------- - (void) useSkippedFuel { fuel -= currentRange; currentRange = 0; } //---------------------------------------------------------------------- - (void) moved { if (currentRange > 0) currentRange--; if (fuel > 0) fuel--; if (currentRange == 0) isDoneTurn = YES; if (order != nil) [order unblocked]; } //---------------------------------------------------------------------- - (BOOL) isFinishedMoving { return isDoneTurn; } //---------------------------------------------------------------------- - (void) setUnitFinishedMoving:(BOOL)flag { if (currentRange > 0 && flag == NO) isDoneTurn = NO; else isDoneTurn = YES; } //---------------------------------------------------------------------- // If this unit has orders, it will try to execute them. // Returns YES if it had orders and was able to move, NO otherwise; //---------------------------------------------------------------------- - (BOOL) tryToMove { BOOL hasMoved = NO; if (order != nil) { Direction dir; [order aboutToMove]; if ([self isFinishedMoving] == NO) { int count = 9; dir = [order nextMove]; while (dir != d_none && hasMoved == NO && count > 0) { count--; hasMoved = [owner tryToMoveSelectedUnitInDirection:dir]; if (hasMoved == NO) { BOOL tryAgain = [order wasBlocked]; if (tryAgain == YES) dir = [order nextMove]; else break; } else { [order unblocked]; } } } else { isDoneTurn = YES; hasMoved = YES; } if ([order isOrderComplete] == YES) [self setOrder:nil]; } if (isDoneTurn == YES) hasMoved = YES; return hasMoved; } //====================================================================== // Human UI support //====================================================================== - (NSString *) statusLine { NSString *orderText; int count; if (order == nil) orderText = @"None"; else orderText = [order orderText]; count = (cargo == nil) ? 0 : [cargo count]; if (unitType == u_fighter) { return [NSString stringWithFormat:@"Loc: %d,%d - %@ %@, Hits: %d/%d, %d/%d%@, Orders: %@", unitLocation.row, unitLocation.column, EMUnitTypeName (unitType), unitName, currentHitPoints, [self maxHitPoints], currentRange, fuel, (isDoneTurn == YES) ? @"*" : @"", orderText]; } else if (unitType == u_transport && count > 0) { return [NSString stringWithFormat:@"Loc: %d,%d - %@ %@ w/%d %@, Hits: %d/%d, %d, Orders: %@", unitLocation.row, unitLocation.column, EMUnitTypeName (unitType), unitName, count, (count == 1) ? @"army" : @"armies", currentHitPoints, [self maxHitPoints], currentRange, orderText]; } else if (unitType == u_carrier && count > 0) { return [NSString stringWithFormat:@"Loc: %d,%d - %@ %@ w/%d fighter%@, Hits: %d/%d, %d, Orders: %@", unitLocation.row, unitLocation.column, EMUnitTypeName (unitType), unitName, [cargo count], (count == 1) ? @"" : @"s", currentHitPoints, [self maxHitPoints], currentRange, orderText]; } return [NSString stringWithFormat:@"Loc: %d,%d - %@ %@, Hits: %d/%d, %d, Orders: %@", unitLocation.row, unitLocation.column, EMUnitTypeName (unitType), unitName, currentHitPoints, [self maxHitPoints], currentRange, orderText]; } //====================================================================== // Commands //====================================================================== //---------------------------------------------------------------------- // Need to set orders to nil, make the ship wait, and then add any // unloaded cargo that can move to the units awaing movement list, being // careful not to add duplicates... //---------------------------------------------------------------------- - (void) unloadShip { if (cargo != nil) { int count = [cargo count]; int l; Unit *unit; for (l = 0; l < count; l++) { unit = [cargo objectAtIndex:l]; [unit setOrder:nil]; [owner activateThisUnit:unit]; } } } //---------------------------------------------------------------------- - (void) destroyUnit:(BOOL)disbanded { Unit *unit; currentHitPoints = 0; isDestroyed = YES; // Disbanded to be YES when disbanding an army to create city garrison. [self setCity:nil]; [self setOnBoardShip:nil]; if (cargo != nil) { unit = [cargo lastObject]; while (unit != nil) { [owner destroyUnit:unit wasDisbanded:NO]; unit = [cargo lastObject]; } } } //---------------------------------------------------------------------- // Returns YES is the unit was loaded aboard this unit, NO otherwise. //---------------------------------------------------------------------- - (BOOL) loadUnit:(Unit *)aUnit { UnitType cargo_ut = [aUnit unitType]; Order *old_order; if ([self isFull] == NO && ( (unitType == u_transport && cargo_ut == u_army) || (unitType == u_carrier && cargo_ut == u_fighter) )) { [aUnit setOnBoardShip:self]; old_order = [aUnit currentOrder]; if (old_order == nil || [old_order isOrderComplete] == YES) { [aUnit setOrder:[[OSentry alloc] initForUnit:aUnit]]; } return YES; } return NO; } //====================================================================== // Combat Support //====================================================================== - (BOOL) hitForDamage:(int)damage { BOOL wasDestroyed = NO; currentHitPoints -= damage; if (currentHitPoints <= 0) { [[self retain] autorelease]; [owner destroyUnit:self wasDisbanded:NO]; wasDestroyed = YES; } else if (cargo != nil) // Make sure cargo still fits! { int count = [cargo count]; int over = 0; int l; if (unitType == u_transport) over = count - 2 * currentHitPoints; else if (unitType == u_carrier) over = count - currentHitPoints; for (l = 0; l < over; l++) [owner destroyUnit:[cargo lastObject] wasDisbanded:NO]; } return wasDestroyed; } //---------------------------------------------------------------------- - (CombatProfile) combatProfileAgainstBombardment:(BOOL)bombarding { CombatProfile profile; int efficiency; efficiency = [owner combatEfficiency]; profile.location = unitLocation; profile.player = [owner playerNumber]; profile.attacking_efficiency = efficiency * [self attackFactor]; profile.defending_efficiency = efficiency * [self defenseFactor:bombarding]; profile.damage_per_hit = [self damagePerHit]; profile.current_hit_points = currentHitPoints; profile.adjacent_players = 0; // filled in by someone else profile.is_a_city = NO; return profile; } //====================================================================== // Other //====================================================================== - (BOOL) isOnBoardShip { return onBoard != nil; } //---------------------------------------------------------------------- - (BOOL) isInCity { return withinCity != nil; } //---------------------------------------------------------------------- // The current management of releasing destroyed units has to be // looked at a bit more closely and handled more carefully. //---------------------------------------------------------------------- - (BOOL) hasBeenDestroyed { return NO; } //---------------------------------------------------------------------- - (City *) city { return withinCity; } //---------------------------------------------------------------------- // A unit may be within a city, or (possibly) on board a ship, but it // can't be on both. // // When a unit enters a city, all the cargo has it's city set, and therefore // leaves its transport. //---------------------------------------------------------------------- - (void) setCity:(City *)aCity { if (withinCity != nil) { [withinCity unitDidExit:self]; withinCity = nil; } if (aCity != nil && onBoard != nil) { [onBoard unitDidExit:self]; onBoard = nil; } if (aCity != nil && cargo != nil) { Unit *unit = [cargo lastObject]; while (unit != nil) { [unit setCity:aCity]; // Carge removed when the new city is set. //[cargo removeLastObject]; unit = [cargo lastObject]; } } withinCity = aCity; if (withinCity != nil) [withinCity unitDidEnter:self]; } //---------------------------------------------------------------------- - (Unit *) shipOnBoard { return onBoard; } //---------------------------------------------------------------------- - (void) setOnBoardShip:(Unit *)ship { if (onBoard != nil) { [onBoard unitDidExit:self]; onBoard = nil; } if (ship != nil && withinCity != nil) { [withinCity unitDidExit:self]; withinCity = nil; } onBoard = ship; if (ship != nil) [ship unitDidEnter:self]; } //---------------------------------------------------------------------- - (void) unitDidExit:(Unit *)aUnit { [cargo removeObject:aUnit]; if (cargo != nil && [cargo count] == 0) [owner unitIconHasChanged:self]; } //---------------------------------------------------------------------- // Assumed that the movement method has already checked that the unit // will fit. //---------------------------------------------------------------------- - (void) unitDidEnter:(Unit *)aUnit { BOOL wasEmpty = [cargo count] == 0; [cargo addObject:aUnit]; if (wasEmpty == YES) [owner unitIconHasChanged:self]; [aUnit setOrder:[[OSentry alloc] initForUnit:aUnit]]; if (order != nil) { [order unitDidEnter:aUnit]; } } @end
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.