Exemplo n.º 1
0
// ////////////////////////////////////////////////////////////////////////////
// pick a droid to send, NULL otherwise.
static DROID* pickADroid(void)
{
	DROID *pD, *ret = NULL;  // ret: dummy initialisation.
	unsigned player = MAX_PLAYERS;
	unsigned i;

	// Pick a random player who has at least one droid.
	for (i = 0; i < 200; ++i)
	{
		unsigned p = gameRand(MAX_PLAYERS);
		if (apsDroidLists[p] != NULL)
		{
			player = p;
			break;
		}
	}

	if (player == MAX_PLAYERS)
	{
		return NULL;  // No players have any droids, with high probability...
	}

	// O(n) where n is number of droids. Slow, but hard to beat on a linked list. (One call of a pick n droids function would be just as fast.)
	i = 0;
	for (pD = apsDroidLists[player]; pD != NULL; pD = pD->psNext)
	{
		if (gameRand(++i) == 0)
		{
			ret = pD;
		}
	}

	return ret;
}
Exemplo n.º 2
0
bool addOilDrum(uint8_t count)
{
	syncDebug("Adding %d oil drums.", count);

	int featureIndex;
	for (featureIndex = 0; featureIndex < numFeatureStats && asFeatureStats[featureIndex].subType != FEAT_OIL_DRUM; ++featureIndex) {}
	if (featureIndex >= numFeatureStats)
	{
		debug(LOG_WARNING, "No oil drum feature!");
		return false;  // Return value ignored.
	}

	for (unsigned n = 0; n < count; ++n)
	{
		uint32_t x, y;
		for (int i = 0; i < 3; ++i)  // try three times
		{
			// Between 10 and mapwidth - 10
			x = gameRand(mapWidth - 20) + 10;
			y = gameRand(mapHeight - 20) + 10;

			if (pickATileGen(&x, &y, LOOK_FOR_EMPTY_TILE, zonedPAT))
			{
				break;
			}
			x = INVALID_XY;
		}
		if (x == INVALID_XY)
		{
			syncDebug("Did not find location for oil drum.");
			debug(LOG_FEATURE, "Unable to find a free location.");
			continue;
		}
		FEATURE *pF = buildFeature(&asFeatureStats[featureIndex], world_coord(x), world_coord(y), false);
		if (pF)
		{
			pF->player = ANYPLAYER;
			syncDebugFeature(pF, '+');
		}
		else
		{
			debug(LOG_ERROR, "Couldn't build oil drum?");
		}
	}
	return true;
}
Exemplo n.º 3
0
// ////////////////////////////////////////////////////////////////////////
// ////////////////////////////////////////////////////////////////////////
// Structure Checking, to ensure smoke and stuff is consistent across machines.
// this func is recursive!
static STRUCTURE *pickAStructure(unsigned player)
{
	STRUCTURE *pS, *ret = NULL;
	unsigned i;

	// O(n) where n is number of structures. Slow, but hard to beat on a linked list.
	i = 0;
	for (pS = apsStructLists[player]; pS != NULL; pS = pS->psNext)
	{
		if (gameRand(++i) == 0)
		{
			ret = pS;
		}
	}

	return ret;
}
Exemplo n.º 4
0
/* Fire a weapon at something */
bool combFire(WEAPON *psWeap, BASE_OBJECT *psAttacker, BASE_OBJECT *psTarget, int weapon_slot)
{
	WEAPON_STATS	*psStats;
	UDWORD                  damLevel;
	UDWORD			firePause;
	SDWORD			longRange;
	DROID			*psDroid = NULL;
	int				compIndex;

	CHECK_OBJECT(psAttacker);
	CHECK_OBJECT(psTarget);
	ASSERT(psWeap != NULL, "Invalid weapon pointer");

	/* Watermelon:dont shoot if the weapon_slot of a vtol is empty */
	if (psAttacker->type == OBJ_DROID && isVtolDroid(((DROID *)psAttacker)))
	{
		if (((DROID *)psAttacker)->sMove.iAttackRuns[weapon_slot] >= getNumAttackRuns(((DROID *)psAttacker), weapon_slot))
		{
			objTrace(psAttacker->id, "VTOL slot %d is empty", weapon_slot);
			return false;
		}
	}

	/* Get the stats for the weapon */
	compIndex = psWeap->nStat;
	ASSERT_OR_RETURN( false , compIndex < numWeaponStats, "Invalid range referenced for numWeaponStats, %d > %d", compIndex, numWeaponStats);
	psStats = asWeaponStats + compIndex;

	// check valid weapon/prop combination
	if (!validTarget(psAttacker, psTarget, weapon_slot))
	{
		return false;
	}

	/*see if reload-able weapon and out of ammo*/
	if (psStats->reloadTime && !psWeap->ammo)
	{
		if (gameTime - psWeap->lastFired < weaponReloadTime(psStats, psAttacker->player))
		{
			return false;
		}
		//reset the ammo level
		psWeap->ammo = psStats->numRounds;
	}

	/* See when the weapon last fired to control it's rate of fire */
	firePause = weaponFirePause(psStats, psAttacker->player);

	// increase the pause if heavily damaged
	switch (psAttacker->type)
	{
	case OBJ_DROID:
		psDroid = (DROID *)psAttacker;
		damLevel = PERCENT(psDroid->body, psDroid->originalBody);
		break;
	case OBJ_STRUCTURE:
		damLevel = PERCENT(((STRUCTURE *)psAttacker)->body, structureBody((STRUCTURE *)psAttacker));
		break;
	default:
		damLevel = 100;
		break;
	}

	if (damLevel < HEAVY_DAMAGE_LEVEL)
	{
		firePause += firePause;
	}

	if (gameTime - psWeap->lastFired <= firePause)
	{
		/* Too soon to fire again */
		return false;
	}

	// add a random delay to the fire
	// With logical updates, a good graphics gard no longer gives a better ROF.
	// TODO Should still replace this with something saner, such as a ±1% random deviation in reload time.
	int fireChance = gameTime - (psWeap->lastFired + firePause);
	if (gameRand(RANDOM_PAUSE) > fireChance)
	{
		return false;
	}

	if (psTarget->visible[psAttacker->player] != UBYTE_MAX)
	{
		// Can't see it - can't hit it
		objTrace(psAttacker->id, "combFire(%u[%s]->%u): Object has no indirect sight of target", psAttacker->id, psStats->pName, psTarget->id);
		return false;
	}

	/* Check we can see the target */
	if (psAttacker->type == OBJ_DROID && !isVtolDroid((DROID *)psAttacker)
	    && (proj_Direct(psStats) || actionInsideMinRange(psDroid, psTarget, psStats)))
	{
		if(!lineOfFire(psAttacker, psTarget, weapon_slot, true))
		{
			// Can't see the target - can't hit it with direct fire
			objTrace(psAttacker->id, "combFire(%u[%s]->%u): Droid has no direct line of sight to target",
			      psAttacker->id, ((DROID *)psAttacker)->aName, psTarget->id);
			return false;
		}
	}
	else if ((psAttacker->type == OBJ_STRUCTURE) &&
			 (((STRUCTURE *)psAttacker)->pStructureType->height == 1) &&
			 proj_Direct(psStats))
	{
		// a bunker can't shoot through walls
		if (!lineOfFire(psAttacker, psTarget, weapon_slot, true))
		{
			// Can't see the target - can't hit it with direct fire
			objTrace(psAttacker->id, "combFire(%u[%s]->%u): Structure has no direct line of sight to target",
			      psAttacker->id, ((STRUCTURE *)psAttacker)->pStructureType->pName, psTarget->id);
			return false;
		}
	}
	else if ( proj_Direct(psStats) )
	{
		// VTOL or tall building
		if (!lineOfFire(psAttacker, psTarget, weapon_slot, false))
		{
			// Can't see the target - can't hit it with direct fire
			objTrace(psAttacker->id, "combFire(%u[%s]->%u): Tall object has no direct line of sight to target",
			      psAttacker->id, psStats->pName, psTarget->id);
			return false;
		}
	}

	Vector3i deltaPos = psTarget->pos - psAttacker->pos;

	// if the turret doesn't turn, check if the attacker is in alignment with the target
	if (psAttacker->type == OBJ_DROID && !psStats->rotate)
	{
		uint16_t targetDir = iAtan2(removeZ(deltaPos));
		int dirDiff = abs(angleDelta(targetDir - psAttacker->rot.direction));
		if (dirDiff > FIXED_TURRET_DIR)
		{
			return false;
		}
	}

	/* Now see if the target is in range  - also check not too near */
	int dist = iHypot(removeZ(deltaPos));
	longRange = proj_GetLongRange(psStats);

	/* modification by CorvusCorax - calculate shooting angle */
	int min_angle = 0;
	// only calculate for indirect shots
	if (!proj_Direct(psStats) && dist > 0)
	{
		min_angle = arcOfFire(psAttacker,psTarget,weapon_slot,true);

		// prevent extremely steep shots
		min_angle = std::min(min_angle, DEG(PROJ_ULTIMATE_PITCH));

		// adjust maximum range of unit if forced to shoot very steep
		if (min_angle > DEG(PROJ_MAX_PITCH))
		{
			//do not allow increase of max range though
			if (iSin(2*min_angle) < iSin(2*DEG(PROJ_MAX_PITCH)))  // If PROJ_MAX_PITCH == 45, then always iSin(2*min_angle) <= iSin(2*DEG(PROJ_MAX_PITCH)), and the test is redundant.
			{
				longRange = longRange * iSin(2*min_angle) / iSin(2*DEG(PROJ_MAX_PITCH));
			}
		}
	}

	int baseHitChance = 0;
	if ((dist <= psStats->shortRange)  && (dist >= psStats->minRange))
	{
		// get weapon chance to hit in the short range
		baseHitChance = weaponShortHit(psStats,psAttacker->player);
	}
	else if ((dist <= longRange && dist >= psStats->minRange)
	         || (psAttacker->type == OBJ_DROID
	             && !proj_Direct(psStats)
	             && actionInsideMinRange(psDroid, psTarget, psStats)))
	{
		// get weapon chance to hit in the long range
		baseHitChance = weaponLongHit(psStats,psAttacker->player);

		// adapt for height adjusted artillery shots
		if (min_angle > DEG(PROJ_MAX_PITCH))
		{
			baseHitChance = baseHitChance * iCos(min_angle) / iCos(DEG(PROJ_MAX_PITCH));
		}
	}
	else
	{
		/* Out of range */
		objTrace(psAttacker->id, "combFire(%u[%s]->%u): Out of range", psAttacker->id, psStats->pName, psTarget->id);
		return false;
	}

	// apply experience accuracy modifiers to the base
	//hit chance, not to the final hit chance
	int resultHitChance = baseHitChance;

	// add the attacker's experience
	if (psAttacker->type == OBJ_DROID)
	{
		SDWORD	level = getDroidEffectiveLevel((DROID *) psAttacker);

		// increase total accuracy by EXP_ACCURACY_BONUS % for each experience level
		resultHitChance += EXP_ACCURACY_BONUS * level * baseHitChance / 100;
	}

	// subtract the defender's experience
	if (psTarget->type == OBJ_DROID)
	{
		SDWORD	level = getDroidEffectiveLevel((DROID *) psTarget);

		// decrease weapon accuracy by EXP_ACCURACY_BONUS % for each experience level
		resultHitChance -= EXP_ACCURACY_BONUS * level * baseHitChance / 100;

	}

	// fire while moving modifiers
	if (psAttacker->type == OBJ_DROID &&
		((DROID *)psAttacker)->sMove.Status != MOVEINACTIVE)
	{
		switch (psStats->fireOnMove)
		{
		case FOM_NO:
			// Can't fire while moving
			return false;
			break;
		case FOM_PARTIAL:
			resultHitChance = FOM_PARTIAL_ACCURACY_PENALTY * resultHitChance / 100;
			break;
		case FOM_YES:
			// can fire while moving
			break;
		}
	}

	/* -------!!! From that point we are sure that we are firing !!!------- */

	/* note when the weapon fired */
	psWeap->lastFired = gameTime;

	/* reduce ammo if salvo */
	if (psStats->reloadTime)
	{
		psWeap->ammo--;
	}

	// increment the shots counter
	psWeap->shotsFired++;

	// predicted X,Y offset per sec
	Vector3i predict = psTarget->pos;

	//Watermelon:Target prediction
	if (isDroid(psTarget))
	{
		DROID *psDroid = castDroid(psTarget);

		int32_t flightTime;
		if (proj_Direct(psStats) || dist <= psStats->minRange)
		{
			flightTime = dist / psStats->flightSpeed;
		}
		else
		{
			int32_t vXY, vZ;  // Unused, we just want the flight time.
			flightTime = projCalcIndirectVelocities(dist, deltaPos.z, psStats->flightSpeed, &vXY, &vZ, min_angle);
		}

		if (psTarget->lastHitWeapon == WSC_EMP)
		{
			int empTime = EMP_DISABLE_TIME - (gameTime - psTarget->timeLastHit);
			CLIP(empTime, 0, EMP_DISABLE_TIME);
			if (empTime >= EMP_DISABLE_TIME * 9/10)
			{
				flightTime = 0;  /* Just hit.  Assume they'll get hit again */
			}
			else
			{
				flightTime = MAX(0, flightTime - empTime);
			}
		}

		predict += Vector3i(iSinCosR(psDroid->sMove.moveDir, psDroid->sMove.speed*flightTime / GAME_TICKS_PER_SEC), 0);
	}

	/* Fire off the bullet to the miss location. The miss is only visible if the player owns the target. (Why? - Per) */
	// What bVisible really does is to make the projectile audible even if it misses you. Since the target is NULL, proj_SendProjectile can't check if it was fired at you.
	bool bVisibleAnyway = psTarget->player == selectedPlayer;

	// see if we were lucky to hit the target
	bool isHit = gameRand(100) <= resultHitChance;
	if (isHit)
	{
		/* Kerrrbaaang !!!!! a hit */
		objTrace(psAttacker->id, "combFire: [%s]->%u: resultHitChance=%d, visibility=%hhu : ", psStats->pName, psTarget->id, resultHitChance, psTarget->visible[psAttacker->player]);
		syncDebug("hit=(%d,%d,%d)", predict.x, predict.y, predict.z);
	}
	else /* Deal with a missed shot */
	{
		const int minOffset = 5;

		int missDist = 2 * (100 - resultHitChance) + minOffset;
		Vector3i miss = Vector3i(iSinCosR(gameRand(DEG(360)), missDist), 0);
		predict += miss;

		psTarget = NULL;  // Missed the target, so don't expect to hit it.

		objTrace(psAttacker->id, "combFire: Missed shot by (%4d,%4d)", miss.x, miss.y);
		syncDebug("miss=(%d,%d,%d)", predict.x, predict.y, predict.z);
	}

	// Make sure we don't pass any negative or out of bounds numbers to proj_SendProjectile
	CLIP(predict.x, 0, world_coord(mapWidth - 1));
	CLIP(predict.y, 0, world_coord(mapHeight - 1));

	proj_SendProjectileAngled(psWeap, psAttacker, psAttacker->player, predict, psTarget, bVisibleAnyway, weapon_slot, min_angle);
	return true;
}
Exemplo n.º 5
0
/* Create a feature on the map */
FEATURE * buildFeature(FEATURE_STATS *psStats, UDWORD x, UDWORD y,BOOL FromSave)
{
	UDWORD		mapX, mapY;
	UDWORD		width,breadth, foundationMin,foundationMax, height;
	UDWORD		startX,startY,max,min;
	SDWORD		i;
	UBYTE		vis;
	//try and create the Feature
	FEATURE* psFeature = createFeature();

	if (psFeature == NULL)
	{
		debug(LOG_WARNING, "Feature couldn't be built.");
		return NULL;
	}
	psFeature->psStats = psStats;
	// features are not in the cluster system
	// this will cause an assert when they still end up there
	psFeature->cluster = ~0;
	//add the feature to the list - this enables it to be drawn whilst being built
	addFeature(psFeature);

	// get the terrain average height
	startX = map_coord(x);
	startY = map_coord(y);
	foundationMin = TILE_MAX_HEIGHT;
	foundationMax = TILE_MIN_HEIGHT;
	for (breadth = 0; breadth < psStats->baseBreadth; breadth++)
	{
		for (width = 0; width < psStats->baseWidth; width++)
		{
			getTileMaxMin(startX + width, startY + breadth, &max, &min);
			if (foundationMin > min)
			{
				foundationMin = min;
			}
			if (foundationMax < max)
			{
				foundationMax = max;
			}
		}
	}
	//return the average of max/min height
	height = (foundationMin + foundationMax) / 2;

	// snap the coords to a tile
	if (!FromSave)
	{
		x = (x & ~TILE_MASK) + psStats->baseWidth  %2 * TILE_UNITS/2;
		y = (y & ~TILE_MASK) + psStats->baseBreadth%2 * TILE_UNITS/2;
	}
	else
	{
		if ((x & TILE_MASK) != psStats->baseWidth  %2 * TILE_UNITS/2 ||
		    (y & TILE_MASK) != psStats->baseBreadth%2 * TILE_UNITS/2)
		{
			debug(LOG_WARNING, "Feature not aligned. position (%d,%d), size (%d,%d)", x, y, psStats->baseWidth, psStats->baseBreadth);
		}
	}

	psFeature->pos.x = x;
	psFeature->pos.y = y;

	/* Dump down the building wrecks at random angles - still looks shit though */
	if(psStats->subType == FEAT_BUILD_WRECK || psStats->subType == FEAT_TREE)
	{
		psFeature->rot.direction = gameRand(DEG_360);
	}
	else
	{
		psFeature->rot.direction = 0;
	}
	psFeature->selected = false;
	psFeature->body = psStats->body;
	psFeature->player = MAX_PLAYERS+1;	//set the player out of range to avoid targeting confusions
	objSensorCache((BASE_OBJECT *)psFeature, NULL);
	objEcmCache((BASE_OBJECT *)psFeature, NULL);
	psFeature->bTargetted = false;
	psFeature->timeLastHit = 0;
	psFeature->lastHitWeapon = WSC_NUM_WEAPON_SUBCLASSES;  // no such weapon

	// it has never been drawn
	psFeature->sDisplay.frameNumber = 0;

	if(getRevealStatus())
	{
		vis = 0;
	}
	else
	{
		if(psStats->visibleAtStart)
		{
  			vis = UBYTE_MAX;
		}
		else
		{
			vis = 0;
		}
	}

	// note that the advanced armour system current unused for features
	for (i = 0; i < NUM_HIT_SIDES; i++)
	{
		int j;

		for (j = 0; j < WC_NUM_WEAPON_CLASSES; j++)
		{
			psFeature->armour[i][j] = psFeature->psStats->armourValue;
		}
	}

	memset(psFeature->seenThisTick, 0, sizeof(psFeature->seenThisTick));
	memset(psFeature->visible, 0, sizeof(psFeature->visible));

	//load into the map data
	mapX = map_coord(x) - psStats->baseWidth/2;
	mapY = map_coord(y) - psStats->baseBreadth/2;

	// set up the imd for the feature
	if(psFeature->psStats->subType==FEAT_BUILD_WRECK)
	{
		psFeature->sDisplay.imd = getRandomWreckageImd();
	}
	else
	{
		psFeature->sDisplay.imd = psStats->psImd;
  	}

	ASSERT_OR_RETURN(NULL, psFeature->sDisplay.imd, "No IMD for feature");		// make sure we have an imd.

	for (width = 0; width < psStats->baseWidth; width++)
	{
		for (breadth = 0; breadth < psStats->baseBreadth; breadth++)
		{
			MAPTILE *psTile = mapTile(mapX + width, mapY + breadth);

			//check not outside of map - for load save game
			ASSERT_OR_RETURN(NULL, mapX + width < mapWidth, "x coord bigger than map width - %s, id = %d", getName(psFeature->psStats->pName), psFeature->id);
			ASSERT_OR_RETURN(NULL, mapY + breadth < mapHeight, "y coord bigger than map height - %s, id = %d", getName(psFeature->psStats->pName), psFeature->id);

			if (width != psStats->baseWidth && breadth != psStats->baseBreadth)
			{
				if (TileHasFeature(psTile))
				{
					FEATURE *psBlock = (FEATURE *)psTile->psObject;

					debug(LOG_ERROR, "%s(%d) already placed at (%d+%d, %d+%d) when trying to place %s(%d) at (%d+%d, %d+%d) - removing it",
					      getName(psBlock->psStats->pName), psBlock->id, map_coord(psBlock->pos.x), psBlock->psStats->baseWidth, map_coord(psBlock->pos.y),
					      psBlock->psStats->baseBreadth, getName(psFeature->psStats->pName), psFeature->id, mapX, psStats->baseWidth, mapY, psStats->baseBreadth);

					removeFeature(psBlock);
				}

				psTile->psObject = (BASE_OBJECT*)psFeature;

				// if it's a tall feature then flag it in the map.
				if (psFeature->sDisplay.imd->max.y > TALLOBJECT_YMAX)
				{
					auxSetBlocking(mapX + width, mapY + breadth, AIR_BLOCKED);
				}

				if (psStats->subType != FEAT_GEN_ARTE && psStats->subType != FEAT_OIL_DRUM && psStats->subType != FEAT_BUILD_WRECK)
				{
					auxSetBlocking(mapX + width, mapY + breadth, FEATURE_BLOCKED);
				}
			}

			if( (!psStats->tileDraw) && (FromSave == false) )
			{
				psTile->height = height;
			}
		}
	}
	psFeature->pos.z = map_TileHeight(mapX,mapY);//jps 18july97

//	// set up the imd for the feature
//	if(psFeature->psStats->subType==FEAT_BUILD_WRECK)
//	{
//		psFeature->sDisplay.imd = wreckageImds[rand()%MAX_WRECKAGE];
//	}
//	else
//	{
//		psFeature->sDisplay.imd = psStats->psImd;
// 	}

	return psFeature;
}
Exemplo n.º 6
0
/* Create a feature on the map */
FEATURE * buildFeature(FEATURE_STATS *psStats, UDWORD x, UDWORD y,bool FromSave)
{
	UDWORD		mapX, mapY;
	UDWORD		width,breadth, foundationMin,foundationMax, height;
	UDWORD		startX,startY,max,min;
	//try and create the Feature
	FEATURE *psFeature = new FEATURE(generateSynchronisedObjectId(), psStats);

	if (psFeature == NULL)
	{
		debug(LOG_WARNING, "Feature couldn't be built.");
		return NULL;
	}
	// features are not in the cluster system
	// this will cause an assert when they still end up there
	psFeature->cluster = ~0;
	//add the feature to the list - this enables it to be drawn whilst being built
	addFeature(psFeature);

	// get the terrain average height
	startX = map_coord(x);
	startY = map_coord(y);
	foundationMin = TILE_MAX_HEIGHT;
	foundationMax = TILE_MIN_HEIGHT;
	for (breadth = 0; breadth < psStats->baseBreadth; breadth++)
	{
		for (width = 0; width < psStats->baseWidth; width++)
		{
			getTileMaxMin(startX + width, startY + breadth, &max, &min);
			if (foundationMin > min)
			{
				foundationMin = min;
			}
			if (foundationMax < max)
			{
				foundationMax = max;
			}
		}
	}
	//return the average of max/min height
	height = (foundationMin + foundationMax) / 2;

	// snap the coords to a tile
	if (!FromSave)
	{
		x = (x & ~TILE_MASK) + psStats->baseWidth  %2 * TILE_UNITS/2;
		y = (y & ~TILE_MASK) + psStats->baseBreadth%2 * TILE_UNITS/2;
	}
	else
	{
		if ((x & TILE_MASK) != psStats->baseWidth  %2 * TILE_UNITS/2 ||
		    (y & TILE_MASK) != psStats->baseBreadth%2 * TILE_UNITS/2)
		{
			debug(LOG_WARNING, "Feature not aligned. position (%d,%d), size (%d,%d)", x, y, psStats->baseWidth, psStats->baseBreadth);
		}
	}

	psFeature->pos.x = x;
	psFeature->pos.y = y;

	if (psStats->subType == FEAT_TREE)
	{
		psFeature->rot.direction = gameRand(DEG_360);
	}
	else
	{
		psFeature->rot.direction = 0;
	}
	psFeature->body = psStats->body;
	psFeature->inFire = false;
	objSensorCache((BASE_OBJECT *)psFeature, NULL);
	objEcmCache((BASE_OBJECT *)psFeature, NULL);

	// it has never been drawn
	psFeature->sDisplay.frameNumber = 0;

	for (int j = 0; j < WC_NUM_WEAPON_CLASSES; j++)
	{
		psFeature->armour[j] = psFeature->psStats->armourValue;
	}

	memset(psFeature->seenThisTick, 0, sizeof(psFeature->seenThisTick));
	memset(psFeature->visible, 0, sizeof(psFeature->visible));

	//load into the map data
	mapX = map_coord(x) - psStats->baseWidth/2;
	mapY = map_coord(y) - psStats->baseBreadth/2;

	// set up the imd for the feature
	psFeature->sDisplay.imd = psStats->psImd;

	ASSERT_OR_RETURN(NULL, psFeature->sDisplay.imd, "No IMD for feature");		// make sure we have an imd.

	for (width = 0; width < psStats->baseWidth; width++)
	{
		for (breadth = 0; breadth < psStats->baseBreadth; breadth++)
		{
			MAPTILE *psTile = mapTile(mapX + width, mapY + breadth);

			//check not outside of map - for load save game
			ASSERT_OR_RETURN(NULL, mapX + width < mapWidth, "x coord bigger than map width - %s, id = %d", getName(psFeature->psStats->pName), psFeature->id);
			ASSERT_OR_RETURN(NULL, mapY + breadth < mapHeight, "y coord bigger than map height - %s, id = %d", getName(psFeature->psStats->pName), psFeature->id);

			if (width != psStats->baseWidth && breadth != psStats->baseBreadth)
			{
				if (TileHasFeature(psTile))
				{
					FEATURE *psBlock = (FEATURE *)psTile->psObject;

					debug(LOG_ERROR, "%s(%d) already placed at (%d+%d, %d+%d) when trying to place %s(%d) at (%d+%d, %d+%d) - removing it",
					      getName(psBlock->psStats->pName), psBlock->id, map_coord(psBlock->pos.x), psBlock->psStats->baseWidth, map_coord(psBlock->pos.y),
					      psBlock->psStats->baseBreadth, getName(psFeature->psStats->pName), psFeature->id, mapX, psStats->baseWidth, mapY, psStats->baseBreadth);

					removeFeature(psBlock);
				}

				psTile->psObject = (BASE_OBJECT*)psFeature;

				// if it's a tall feature then flag it in the map.
				if (psFeature->sDisplay.imd->max.y > TALLOBJECT_YMAX)
				{
					auxSetBlocking(mapX + width, mapY + breadth, AIR_BLOCKED);
				}

				if (psStats->subType != FEAT_GEN_ARTE && psStats->subType != FEAT_OIL_DRUM)
				{
					auxSetBlocking(mapX + width, mapY + breadth, FEATURE_BLOCKED);
				}
			}

			if( (!psStats->tileDraw) && (FromSave == false) )
			{
				psTile->height = height;
			}
		}
	}
	psFeature->pos.z = map_TileHeight(mapX,mapY);//jps 18july97

	return psFeature;
}
Exemplo n.º 7
0
/* Fire a weapon at something */
bool combFire(WEAPON *psWeap, BASE_OBJECT *psAttacker, BASE_OBJECT *psTarget, int weapon_slot)
{
	WEAPON_STATS	*psStats;
	UDWORD			firePause;
	SDWORD			longRange;
	int				compIndex;

	CHECK_OBJECT(psAttacker);
	CHECK_OBJECT(psTarget);
	ASSERT(psWeap != NULL, "Invalid weapon pointer");

	/* Watermelon:dont shoot if the weapon_slot of a vtol is empty */
	if (psAttacker->type == OBJ_DROID && isVtolDroid(((DROID *)psAttacker)))
	{
		if (psWeap->usedAmmo >= getNumAttackRuns(((DROID *)psAttacker), weapon_slot))
		{
			objTrace(psAttacker->id, "VTOL slot %d is empty", weapon_slot);
			return false;
		}
	}

	/* Get the stats for the weapon */
	compIndex = psWeap->nStat;
	ASSERT_OR_RETURN( false , compIndex < numWeaponStats, "Invalid range referenced for numWeaponStats, %d > %d", compIndex, numWeaponStats);
	psStats = asWeaponStats + compIndex;

	// check valid weapon/prop combination
	if (!validTarget(psAttacker, psTarget, weapon_slot))
	{
		return false;
	}

	unsigned fireTime = gameTime - deltaGameTime + 1;  // Can fire earliest at the start of the tick.

	// See if reloadable weapon.
	if (psStats->reloadTime)
	{
		unsigned reloadTime = psWeap->lastFired + weaponReloadTime(psStats, psAttacker->player);
		if (psWeap->ammo == 0)  // Out of ammo?
		{
			fireTime = std::max(fireTime, reloadTime);  // Have to wait for weapon to reload before firing.
			if (gameTime < fireTime)
			{
				return false;
			}
		}

		if (reloadTime <= fireTime)
		{
			//reset the ammo level
			psWeap->ammo = psStats->numRounds;
		}
	}

	/* See when the weapon last fired to control it's rate of fire */
	firePause = weaponFirePause(psStats, psAttacker->player);
	firePause = std::max(firePause, 1u);  // Don't shoot infinitely many shots at once.
	fireTime = std::max(fireTime, psWeap->lastFired + firePause);

	if (gameTime < fireTime)
	{
		/* Too soon to fire again */
		return false;
	}

	if (psTarget->visible[psAttacker->player] != UBYTE_MAX)
	{
		// Can't see it - can't hit it
		objTrace(psAttacker->id, "combFire(%u[%s]->%u): Object has no indirect sight of target", psAttacker->id, psStats->pName, psTarget->id);
		return false;
	}

	/* Check we can hit the target */
	bool tall = (psAttacker->type == OBJ_DROID && isVtolDroid((DROID *)psAttacker))
		    || (psAttacker->type == OBJ_STRUCTURE && ((STRUCTURE *)psAttacker)->pStructureType->height > 1);
	if (proj_Direct(psStats) && !lineOfFire(psAttacker, psTarget, weapon_slot, tall))
	{
		// Can't see the target - can't hit it with direct fire
		objTrace(psAttacker->id, "combFire(%u[%s]->%u): No direct line of sight to target",
		         psAttacker->id, objInfo(psAttacker), psTarget->id);
		return false;
	}

	Vector3i deltaPos = psTarget->pos - psAttacker->pos;

	// if the turret doesn't turn, check if the attacker is in alignment with the target
	if (psAttacker->type == OBJ_DROID && !psStats->rotate)
	{
		uint16_t targetDir = iAtan2(removeZ(deltaPos));
		int dirDiff = abs(angleDelta(targetDir - psAttacker->rot.direction));
		if (dirDiff > FIXED_TURRET_DIR)
		{
			return false;
		}
	}

	/* Now see if the target is in range  - also check not too near */
	int dist = iHypot(removeZ(deltaPos));
	longRange = proj_GetLongRange(psStats);

	int min_angle = 0;
	// Calculate angle for indirect shots
	if (!proj_Direct(psStats) && dist > 0)
	{
		min_angle = arcOfFire(psAttacker,psTarget,weapon_slot,true);

		// prevent extremely steep shots
		min_angle = std::min(min_angle, DEG(PROJ_ULTIMATE_PITCH));

		// adjust maximum range of unit if forced to shoot very steep
		if (min_angle > DEG(PROJ_MAX_PITCH))
		{
			//do not allow increase of max range though
			if (iSin(2*min_angle) < iSin(2*DEG(PROJ_MAX_PITCH)))  // If PROJ_MAX_PITCH == 45, then always iSin(2*min_angle) <= iSin(2*DEG(PROJ_MAX_PITCH)), and the test is redundant.
			{
				longRange = longRange * iSin(2*min_angle) / iSin(2*DEG(PROJ_MAX_PITCH));
			}
		}
	}

	int baseHitChance = 0;
	if (dist <= longRange && dist >= psStats->minRange)
	{
		// get weapon chance to hit in the long range
		baseHitChance = weaponLongHit(psStats,psAttacker->player);

		// adapt for height adjusted artillery shots
		if (min_angle > DEG(PROJ_MAX_PITCH))
		{
			baseHitChance = baseHitChance * iCos(min_angle) / iCos(DEG(PROJ_MAX_PITCH));
		}
	}
	else
	{
		/* Out of range */
		objTrace(psAttacker->id, "combFire(%u[%s]->%u): Out of range", psAttacker->id, psStats->pName, psTarget->id);
		return false;
	}

	// apply experience accuracy modifiers to the base
	//hit chance, not to the final hit chance
	int resultHitChance = baseHitChance;

	// add the attacker's experience
	if (psAttacker->type == OBJ_DROID)
	{
		SDWORD	level = getDroidEffectiveLevel((DROID *) psAttacker);

		// increase total accuracy by EXP_ACCURACY_BONUS % for each experience level
		resultHitChance += EXP_ACCURACY_BONUS * level * baseHitChance / 100;
	}

	// subtract the defender's experience
	if (psTarget->type == OBJ_DROID)
	{
		SDWORD	level = getDroidEffectiveLevel((DROID *) psTarget);

		// decrease weapon accuracy by EXP_ACCURACY_BONUS % for each experience level
		resultHitChance -= EXP_ACCURACY_BONUS * level * baseHitChance / 100;

	}

	// fire while moving modifiers
	if (psAttacker->type == OBJ_DROID &&
		((DROID *)psAttacker)->sMove.Status != MOVEINACTIVE)
	{
		switch (psStats->fireOnMove)
		{
		case FOM_NO:
			// Can't fire while moving
			return false;
			break;
		case FOM_PARTIAL:
			resultHitChance = FOM_PARTIAL_ACCURACY_PENALTY * resultHitChance / 100;
			break;
		case FOM_YES:
			// can fire while moving
			break;
		}
	}

	/* -------!!! From that point we are sure that we are firing !!!------- */

	// Add a random delay to the next shot.
	// TODO Add deltaFireTime to the time it takes to fire next. If just adding to psWeap->lastFired, it might put it in the future, causing assertions. And if not sometimes putting it in the future, the fire rate would be lower than advertised.
	//int fireJitter = firePause/100;  // ±1% variation in fire rate.
	//int deltaFireTime = gameRand(fireJitter*2 + 1) - fireJitter;

	/* note when the weapon fired */
	psWeap->lastFired = fireTime;

	/* reduce ammo if salvo */
	if (psStats->reloadTime)
	{
		psWeap->ammo--;
	}

	// increment the shots counter
	psWeap->shotsFired++;

	// predicted X,Y offset per sec
	Vector3i predict = psTarget->pos;

	//Watermelon:Target prediction
	if (isDroid(psTarget) && castDroid(psTarget)->sMove.bumpTime == 0)
	{
		DROID *psDroid = castDroid(psTarget);

		int32_t flightTime;
		if (proj_Direct(psStats) || dist <= psStats->minRange)
		{
			flightTime = dist * GAME_TICKS_PER_SEC / psStats->flightSpeed;
		}
		else
		{
			int32_t vXY, vZ;  // Unused, we just want the flight time.
			flightTime = projCalcIndirectVelocities(dist, deltaPos.z, psStats->flightSpeed, &vXY, &vZ, min_angle);
		}

		if (psTarget->lastHitWeapon == WSC_EMP)
		{
			int playerEmpTime = getEmpDisableTime(psTarget->player);
			int empTime = playerEmpTime - (gameTime - psTarget->timeLastHit);
			CLIP(empTime, 0, playerEmpTime);
			if (empTime >= playerEmpTime * 9/10)
			{
				flightTime = 0;  /* Just hit.  Assume they'll get hit again */
			}
			else
			{
				flightTime = MAX(0, flightTime - empTime);
			}
		}

		predict += Vector3i(iSinCosR(psDroid->sMove.moveDir, psDroid->sMove.speed*flightTime / GAME_TICKS_PER_SEC), 0);
		if (!isFlying(psDroid))
		{
			predict.z = map_Height(removeZ(predict));  // Predict that the object will be on the ground.
		}
	}

	/* Fire off the bullet to the miss location. The miss is only visible if the player owns the target. (Why? - Per) */
	// What bVisible really does is to make the projectile audible even if it misses you. Since the target is NULL, proj_SendProjectile can't check if it was fired at you.
	bool bVisibleAnyway = psTarget->player == selectedPlayer;

	// see if we were lucky to hit the target
	bool isHit = gameRand(100) <= resultHitChance;
	if (isHit)
	{
		/* Kerrrbaaang !!!!! a hit */
		objTrace(psAttacker->id, "combFire: [%s]->%u: resultHitChance=%d, visibility=%d", psStats->pName, psTarget->id, resultHitChance, (int)psTarget->visible[psAttacker->player]);
		syncDebug("hit=(%d,%d,%d)", predict.x, predict.y, predict.z);
	}
	else /* Deal with a missed shot */
	{
		const int minOffset = 5;

		int missDist = 2 * (100 - resultHitChance) + minOffset;
		Vector3i miss = Vector3i(iSinCosR(gameRand(DEG(360)), missDist), 0);
		predict += miss;

		psTarget = NULL;  // Missed the target, so don't expect to hit it.

		objTrace(psAttacker->id, "combFire: Missed shot by (%4d,%4d)", miss.x, miss.y);
		syncDebug("miss=(%d,%d,%d)", predict.x, predict.y, predict.z);
	}

	// Make sure we don't pass any negative or out of bounds numbers to proj_SendProjectile
	CLIP(predict.x, 0, world_coord(mapWidth - 1));
	CLIP(predict.y, 0, world_coord(mapHeight - 1));

	proj_SendProjectileAngled(psWeap, psAttacker, psAttacker->player, predict, psTarget, bVisibleAnyway, weapon_slot, min_angle, fireTime);
	return true;
}
Exemplo n.º 8
0
/* Create a feature on the map */
FEATURE * buildFeature(FEATURE_STATS *psStats, UDWORD x, UDWORD y,bool FromSave)
{
	//try and create the Feature
	FEATURE *psFeature = new FEATURE(generateSynchronisedObjectId(), psStats);

	if (psFeature == NULL)
	{
		debug(LOG_WARNING, "Feature couldn't be built.");
		return NULL;
	}
	// features are not in the cluster system
	// this will cause an assert when they still end up there
	psFeature->cluster = ~0;
	//add the feature to the list - this enables it to be drawn whilst being built
	addFeature(psFeature);

	// snap the coords to a tile
	if (!FromSave)
	{
		x = (x & ~TILE_MASK) + psStats->baseWidth  %2 * TILE_UNITS/2;
		y = (y & ~TILE_MASK) + psStats->baseBreadth%2 * TILE_UNITS/2;
	}
	else
	{
		if ((x & TILE_MASK) != psStats->baseWidth  %2 * TILE_UNITS/2 ||
		    (y & TILE_MASK) != psStats->baseBreadth%2 * TILE_UNITS/2)
		{
			debug(LOG_WARNING, "Feature not aligned. position (%d,%d), size (%d,%d)", x, y, psStats->baseWidth, psStats->baseBreadth);
		}
	}

	psFeature->pos.x = x;
	psFeature->pos.y = y;

	StructureBounds b = getStructureBounds(psFeature);

	// get the terrain average height
	int foundationMin = INT32_MAX;
	int foundationMax = INT32_MIN;
	for (int breadth = 0; breadth <= b.size.y; ++breadth)
	{
		for (int width = 0; width <= b.size.x; ++width)
		{
			int h = map_TileHeight(b.map.x + width, b.map.y + breadth);
			foundationMin = std::min(foundationMin, h);
			foundationMax = std::max(foundationMax, h);
		}
	}
	//return the average of max/min height
	int height = (foundationMin + foundationMax) / 2;

	if (psStats->subType == FEAT_TREE)
	{
		psFeature->rot.direction = gameRand(DEG_360);
	}
	else
	{
		psFeature->rot.direction = 0;
	}
	psFeature->body = psStats->body;
	psFeature->periodicalDamageStart = 0;
	psFeature->periodicalDamage = 0;

	// it has never been drawn
	psFeature->sDisplay.frameNumber = 0;

	memset(psFeature->seenThisTick, 0, sizeof(psFeature->seenThisTick));
	memset(psFeature->visible, 0, sizeof(psFeature->visible));

	// set up the imd for the feature
	psFeature->sDisplay.imd = psStats->psImd;

	ASSERT_OR_RETURN(NULL, psFeature->sDisplay.imd, "No IMD for feature");		// make sure we have an imd.

	for (int breadth = 0; breadth < b.size.y; ++breadth)
	{
		for (int width = 0; width < b.size.x; ++width)
		{
			MAPTILE *psTile = mapTile(b.map.x + width, b.map.y + breadth);

			//check not outside of map - for load save game
			ASSERT_OR_RETURN(NULL, b.map.x + width < mapWidth, "x coord bigger than map width - %s, id = %d", getName(psFeature->psStats), psFeature->id);
			ASSERT_OR_RETURN(NULL, b.map.y + breadth < mapHeight, "y coord bigger than map height - %s, id = %d", getName(psFeature->psStats), psFeature->id);

			if (width != psStats->baseWidth && breadth != psStats->baseBreadth)
			{
				if (TileHasFeature(psTile))
				{
					FEATURE *psBlock = (FEATURE *)psTile->psObject;

					debug(LOG_ERROR, "%s(%d) already placed at (%d+%d, %d+%d) when trying to place %s(%d) at (%d+%d, %d+%d) - removing it",
					      getName(psBlock->psStats), psBlock->id, map_coord(psBlock->pos.x), psBlock->psStats->baseWidth, map_coord(psBlock->pos.y),
					      psBlock->psStats->baseBreadth, getName(psFeature->psStats), psFeature->id, b.map.x, b.size.x, b.map.y, b.size.y);

					removeFeature(psBlock);
				}

				psTile->psObject = (BASE_OBJECT*)psFeature;

				// if it's a tall feature then flag it in the map.
				if (psFeature->sDisplay.imd->max.y > TALLOBJECT_YMAX)
				{
					auxSetBlocking(b.map.x + width, b.map.y + breadth, AIR_BLOCKED);
				}

				if (psStats->subType != FEAT_GEN_ARTE && psStats->subType != FEAT_OIL_DRUM)
				{
					auxSetBlocking(b.map.x + width, b.map.y + breadth, FEATURE_BLOCKED);
				}
			}

			if( (!psStats->tileDraw) && (FromSave == false) )
			{
				psTile->height = height;
			}
		}
	}
	psFeature->pos.z = map_TileHeight(b.map.x, b.map.y);//jps 18july97

	return psFeature;
}
Exemplo n.º 9
0
///////////////////////////////////////////////////////////////////////////////
// splatter artifact gifts randomly about.
void  addMultiPlayerRandomArtifacts(uint8_t quantity, FEATURE_TYPE type)
{
	FEATURE		*pF = NULL;
	int			i, featureStat, count;
	uint32_t	x, y;
	uint8_t		player = ANYPLAYER;

	debug(LOG_FEATURE, "Sending %u artifact(s) type: (%s)", quantity, feature_names[type]);
	NETbeginEncode(NET_ARTIFACTS, NET_ALL_PLAYERS);
		NETuint8_t(&quantity);
		NETenum(&type);

		for(featureStat = 0; featureStat < numFeatureStats && asFeatureStats[featureStat].subType != type; featureStat++);

		ASSERT(mapWidth > 20, "map not big enough");
		ASSERT(mapHeight > 20, "map not big enough");

		for (count = 0; count < quantity; count++)
		{
			for (i = 0; i < 3; i++) // try three times
			{
				// Between 10 and mapwidth - 10
				x = (gameRand(mapWidth - 20)) + 10;
				y = (gameRand(mapHeight - 20)) + 10;

				if (pickATileGen(&x, &y, LOOK_FOR_EMPTY_TILE, zonedPAT))
				{
					break;
				}
				else if (i == 2)
				{
					debug(LOG_FEATURE, "Unable to find a free location after 3 tries; giving up.");
					x = INVALID_XY;
				}
			}
			if (x != INVALID_XY) // at least one of the tries succeeded
			{
				pF = buildFeature(asFeatureStats + featureStat, world_coord(x), world_coord(y), false);
				if (pF)
				{
					pF->player = player;
				}
				else
				{
					x = INVALID_XY;
				}
			}

			NETuint32_t(&x);
			NETuint32_t(&y);
			if (pF)
			{
				NETuint32_t(&pF->id);
			}
			else
			{
				NETuint32_t(&x); // just give them a dummy value; it'll never be used
			}
			NETuint8_t(&player);
		}

	NETend();
}