void UITooltipDraw(GraphicsDevice *device, Vec2i pos, const char *s) { Vec2i bgSize = TextGetSize(s); pos = Vec2iAdd(pos, Vec2iNew(10, 10)); // add offset DrawRectangle( device, Vec2iAdd(pos, Vec2iScale(Vec2iUnit(), -TOOLTIP_PADDING)), Vec2iAdd(bgSize, Vec2iScale(Vec2iUnit(), 2 * TOOLTIP_PADDING)), bgColor, 0); TextString(&gTextManager, s, device, pos); }
void UITooltipDraw(GraphicsDevice *device, Vec2i pos, const char *s) { Vec2i bgSize = FontStrSize(s); pos = Vec2iAdd(pos, Vec2iNew(10, 10)); // add offset DrawRectangle( device, Vec2iAdd(pos, Vec2iScale(Vec2iUnit(), -TOOLTIP_PADDING)), Vec2iAdd(bgSize, Vec2iScale(Vec2iUnit(), 2 * TOOLTIP_PADDING)), bgColor, 0); FontStr(s, pos); }
static int FindWallRun( const Map *map, const Vec2i mid, const Vec2i d, const int len) { int run = 0; int next = 0; bool plus = false; // Find the wall run by starting from a midpoint and expanding outwards in // both directions, in a series 0, 1, -1, 2, -2... for (int i = 0; i < len; i++, run++) { // Check if this is a wall so we can add a door here // Also check if the two tiles aside are not walls // Note: we must look for runs if (plus) { next += i; } else { next -= i; } const Vec2i v = Vec2iAdd(mid, Vec2iScale(d, next)); plus = !plus; if (IMapGet(map, v) != MAP_WALL || IMapGet(map, Vec2iNew(v.x + d.y, v.y + d.x)) == MAP_WALL || IMapGet(map, Vec2iNew(v.x - d.y, v.y - d.x)) == MAP_WALL) { break; } } return run; }
static void DoDamageCharacter( const Vec2i hitVector, const int power, const int flags, const int playerUID, const int uid, TActor *actor, const special_damage_e special) { // Create events: hit, damage, score CASSERT(actor->isInUse, "Cannot damage nonexistent player"); CASSERT(CanHitCharacter(flags, uid, actor), "damaging undamageable actor"); if (ConfigGetBool(&gConfig, "Game.ShotsPushback")) { GameEvent ei = GameEventNew(GAME_EVENT_ACTOR_IMPULSE); ei.u.ActorImpulse.UID = actor->uid; ei.u.ActorImpulse.Vel = Vec2i2Net(Vec2iScaleDiv( Vec2iScale(hitVector, power), SHOT_IMPULSE_DIVISOR)); ei.u.ActorImpulse.Pos = Vec2i2Net(actor->Pos); GameEventsEnqueue(&gGameEvents, ei); } const bool canDamage = CanDamageCharacter(flags, playerUID, uid, actor, special); GameEvent e = GameEventNew(GAME_EVENT_ACTOR_HIT); e.u.ActorHit.UID = actor->uid; e.u.ActorHit.PlayerUID = actor->PlayerUID; e.u.ActorHit.HitterPlayerUID = playerUID; e.u.ActorHit.Special = special; e.u.ActorHit.Power = canDamage ? power : 0; e.u.ActorHit.Vel = Vec2i2Net(hitVector); GameEventsEnqueue(&gGameEvents, e); if (canDamage) { // Don't score for friendly or player hits const bool isFriendly = (actor->flags & FLAGS_GOOD_GUY) || (!IsPVP(gCampaign.Entry.Mode) && actor->PlayerUID >= 0); if (playerUID >= 0 && power != 0 && !isFriendly) { // Calculate score based on // if they hit a penalty character e = GameEventNew(GAME_EVENT_SCORE); e.u.Score.PlayerUID = playerUID; if (actor->flags & FLAGS_PENALTY) { e.u.Score.Score = PENALTY_MULTIPLIER * power; } else { e.u.Score.Score = power; } GameEventsEnqueue(&gGameEvents, e); } } }
Vec2i GetWallBounceFullPos( const Vec2i startFull, const Vec2i newFull, Vec2i *velFull) { CASSERT(velFull != NULL, "need velocity for wall bouncing"); Vec2i newReal = Vec2iFull2Real(newFull); if (!ShootWall(newReal.x, newReal.y)) { return newFull; } Vec2i startRealPos = Vec2iFull2Real(startFull); Vec2i bounceFull = startFull; if (!ShootWall(startRealPos.x, newReal.y)) { bounceFull.y = newFull.y; velFull->x *= -1; } else if (!ShootWall(newReal.x, startRealPos.y)) { bounceFull.x = newFull.x; velFull->y *= -1; } else { *velFull = Vec2iScale(*velFull, -1); // Keep bouncing back if it's inside a wall // However, do not bounce more than half a tile's size if (!Vec2iIsZero(*velFull)) { Vec2i bounceReal = newReal; const int maxBounces = MAX( velFull->x / 256 / TILE_WIDTH, velFull->y / 256 / TILE_HEIGHT); for (int i = 0; i < maxBounces && MapIsRealPosIn(&gMap, bounceReal) && ShootWall(bounceReal.x, bounceReal.y); i++) { bounceFull = Vec2iAdd(bounceFull, *velFull); bounceReal = Vec2iFull2Real(bounceFull); } // If still colliding wall or outside map, // can't recover from this point; zero velocity and return if (!MapIsRealPosIn(&gMap, bounceReal) || ShootWall(bounceReal.x, bounceReal.y)) { *velFull = Vec2iZero(); return startFull; } } } return bounceFull; }
static void AddBrass( const GunDescription *g, const direction_e d, const Vec2i pos) { CASSERT(g->Brass, "Cannot create brass for no-brass weapon"); GameEvent e = GameEventNew(GAME_EVENT_ADD_PARTICLE); e.u.AddParticle.Class = g->Brass; double x, y; const double radians = dir2radians[d]; GetVectorsForRadians(radians, &x, &y); const Vec2i ejectionPortOffset = Vec2iReal2Full(Vec2iScale(Vec2iNew( (int)round(x), (int)round(y)), 7)); const Vec2i muzzleOffset = GunGetMuzzleOffset(g, d); const Vec2i muzzlePosition = Vec2iAdd(pos, muzzleOffset); e.u.AddParticle.FullPos = Vec2iMinus(muzzlePosition, ejectionPortOffset); e.u.AddParticle.Z = g->MuzzleHeight; e.u.AddParticle.Vel = Vec2iScaleDiv( GetFullVectorsForRadians(radians + PI / 2), 3); e.u.AddParticle.Vel.x += (rand() % 128) - 64; e.u.AddParticle.Vel.y += (rand() % 128) - 64; e.u.AddParticle.Angle = RAND_DOUBLE(0, PI * 2); e.u.AddParticle.DZ = (rand() % 6) + 6; e.u.AddParticle.Spin = RAND_DOUBLE(-0.1, 0.1); GameEventsEnqueue(&gGameEvents, e); }
static void UIObjectDrawAndAddChildren( UIObject *o, GraphicsDevice *g, Vec2i pos, Vec2i mouse, CArray *objs) { if (!o) { return; } if (o->CheckVisible) { o->CheckVisible(o, o->Data); } if (!o->IsVisible) { return; } int isHighlighted = UIObjectIsHighlighted(o); Vec2i oPos = Vec2iAdd(pos, o->Pos); switch (o->Type) { case UITYPE_LABEL: { const char *text = LabelGetText(o); if (!text) { break; } color_t textMask = isHighlighted ? colorRed : colorWhite; FontStrMaskWrap(text, oPos, textMask, o->Size.x); } break; case UITYPE_TEXTBOX: { int isText = !!o->u.Textbox.TextLinkFunc; const char *text = isText ? o->u.Textbox.TextLinkFunc(o, o->Data) : NULL; int isEmptyText = !isText || !text || strlen(text) == 0; color_t bracketMask = isHighlighted ? colorRed : colorWhite; color_t textMask = isEmptyText ? colorGray : colorWhite; int oPosX = oPos.x; if (isEmptyText) { text = o->u.Textbox.Hint; } if (!o->u.Textbox.IsEditable) { textMask = bracketMask; } if (o->u.Textbox.IsEditable) { oPos = FontChMask('>', oPos, bracketMask); } oPos = FontStrMaskWrap( text, oPos, textMask, o->Pos.x + o->Size.x - oPosX); if (o->u.Textbox.IsEditable) { oPos = FontChMask('<', oPos, bracketMask); } oPos.x = oPosX; } break; case UITYPE_TAB: if (o->Children.size > 0) { color_t textMask = isHighlighted ? colorRed : colorWhite; char **labelp = CArrayGet(&o->u.Tab.Labels, o->u.Tab.Index); UIObject **objp = CArrayGet(&o->Children, o->u.Tab.Index); FontStrMaskWrap( *labelp, Vec2iAdd(pos, o->Pos), textMask, o->Size.x); if (!((*objp)->Flags & UI_ENABLED_WHEN_PARENT_HIGHLIGHTED_ONLY) || isHighlighted) { UIObjectDrawAndAddChildren(*objp, g, oPos, mouse, objs); } } break; case UITYPE_BUTTON: { int isDown = o->u.Button.IsDownFunc && o->u.Button.IsDownFunc(o->Data); BlitMasked( g, o->u.Button.Pic, oPos, isDown ? colorGray : colorWhite, 1); } break; case UITYPE_CONTEXT_MENU: { // Draw background DrawRectangle( g, Vec2iAdd(oPos, Vec2iScale(Vec2iUnit(), -TOOLTIP_PADDING)), Vec2iAdd(o->Size, Vec2iScale(Vec2iUnit(), 2 * TOOLTIP_PADDING)), menuBGColor, 0); // Find if mouse over any children, and draw highlight for (int i = 0; i < (int)o->Children.size; i++) { UIObject *child = *(UIObject **)CArrayGet(&o->Children, i); if (IsInside(mouse, Vec2iAdd(oPos, child->Pos), child->Size)) { DrawRectangle( g, Vec2iAdd(oPos, child->Pos), child->Size, hiliteColor, 0); } } } break; case UITYPE_CUSTOM: o->u.CustomDrawFunc(o, g, pos, o->Data); break; default: // do nothing break; } // add children // Note: tab type draws its own children (one) if (o->Type != UITYPE_TAB && objs != NULL) { size_t i; UIObject **childPtr = o->Children.data; for (i = 0; i < o->Children.size; i++, childPtr++) { if (!((*childPtr)->Flags & UI_ENABLED_WHEN_PARENT_HIGHLIGHTED_ONLY) || isHighlighted) { UIObjectDrawContext c; c.obj = *childPtr; c.pos = oPos; CArrayPushBack(objs, &c); } } } }
// Perform LOS by casting rays from the centre to the edges, terminating // whenever an obstruction or out-of-range is reached. void DrawBufferLOS(DrawBuffer *buffer, Vec2i center) { int sightRange = gConfig.Game.SightRange; // Note: can be zero LOSData data; data.b = buffer; data.center.x = center.x / TILE_WIDTH - buffer->xStart; data.center.y = center.y / TILE_HEIGHT - buffer->yStart; data.sightRange2 = sightRange * sightRange; // First mark center tile and all adjacent tiles as visible // +-+-+-+ // |V|V|V| // +-+-+-+ // |V|C|V| // +-+-+-+ // |V|V|V| (C=center, V=visible) // +-+-+-+ Vec2i end; for (end.x = data.center.x - 1; end.x < data.center.x + 2; end.x++) { for (end.y = data.center.y - 1; end.y < data.center.y + 2; end.y++) { Tile *tile = GetTile(buffer, end); if (tile) { tile->flags |= MAPTILE_IS_VISIBLE; } } } // Work out the perimeter of the LOS casts Vec2i origin = Vec2iZero(); if (sightRange > 0) { // Limit the perimeter to the sight range origin.x = MAX(origin.x, data.center.x - sightRange); origin.y = MAX(origin.y, data.center.y - sightRange); } Vec2i perimSize = Vec2iScale(Vec2iMinus(data.center, origin), 2); // Start from the top-left cell, and proceed clockwise around end = origin; HasClearLineData lineData; lineData.IsBlocked = IsNextTileBlockedAndSetVisibility; lineData.data = &data; // Top edge for (; end.x < origin.x + perimSize.x; end.x++) { HasClearLineXiaolinWu(data.center, end, &lineData); } // right edge for (; end.y < origin.y + perimSize.y; end.y++) { HasClearLineXiaolinWu(data.center, end, &lineData); } // bottom edge for (; end.x > origin.x; end.x--) { HasClearLineXiaolinWu(data.center, end, &lineData); } // left edge for (; end.y > origin.y; end.y--) { HasClearLineXiaolinWu(data.center, end, &lineData); } // Second pass: make any non-visible obstructions that are adjacent to // visible non-obstructions visible too // This is to ensure runs of walls stay visible for (end.y = origin.y; end.y < origin.y + perimSize.y; end.y++) { for (end.x = origin.x; end.x < origin.x + perimSize.x; end.x++) { Tile *tile = GetTile(buffer, end); if (!tile || !(tile->flags & MAPTILE_NO_SEE)) { continue; } // Check sight range if (data.sightRange2 > 0 && DistanceSquared(data.center, end) >= data.sightRange2) { continue; } SetObstructionVisible(buffer, end, tile); } } }
void BulletAdd(const NAddBullet add) { const Vec2i pos = Net2Vec2i(add.MuzzlePos); // Find an empty slot in mobobj list TMobileObject *obj = NULL; int i; for (i = 0; i < (int)gMobObjs.size; i++) { TMobileObject *m = CArrayGet(&gMobObjs, i); if (!m->isInUse) { obj = m; break; } } if (obj == NULL) { TMobileObject m; memset(&m, 0, sizeof m); CArrayPushBack(&gMobObjs, &m); i = (int)gMobObjs.size - 1; obj = CArrayGet(&gMobObjs, i); } memset(obj, 0, sizeof *obj); obj->UID = add.UID; obj->bulletClass = StrBulletClass(add.BulletClass); obj->x = pos.x; obj->y = pos.y; obj->z = add.MuzzleHeight; obj->dz = add.Elevation; obj->vel = Vec2iFull2Real(Vec2iScale( GetFullVectorsForRadians(add.Angle), RAND_INT(obj->bulletClass->SpeedLow, obj->bulletClass->SpeedHigh))); if (obj->bulletClass->SpeedScale) { obj->vel.y = obj->vel.y * TILE_WIDTH / TILE_HEIGHT; } obj->PlayerUID = add.PlayerUID; obj->ActorUID = add.ActorUID; obj->range = RAND_INT( obj->bulletClass->RangeLow, obj->bulletClass->RangeHigh); obj->flags = add.Flags; if (obj->bulletClass->HurtAlways) { obj->flags |= FLAGS_HURTALWAYS; } obj->tileItem.kind = KIND_MOBILEOBJECT; obj->tileItem.id = i; obj->isInUse = true; obj->tileItem.x = obj->tileItem.y = -1; obj->tileItem.getPicFunc = NULL; obj->tileItem.getActorPicsFunc = NULL; obj->tileItem.drawFunc = NULL; obj->tileItem.drawData.MobObjId = i; obj->tileItem.CPic = obj->bulletClass->CPic; obj->tileItem.CPicFunc = GetBulletDrawContext; obj->tileItem.size = obj->bulletClass->Size; obj->tileItem.ShadowSize = obj->bulletClass->ShadowSize; obj->updateFunc = UpdateBullet; MapTryMoveTileItem(&gMap, &obj->tileItem, Vec2iFull2Real(pos)); }
bool UpdateBullet(TMobileObject *obj, const int ticks) { obj->count += ticks; obj->soundLock = MAX(0, obj->soundLock - ticks); obj->specialLock = MAX(0, obj->specialLock - ticks); if (obj->count < obj->bulletClass->Delay) { return true; } if (obj->range >= 0 && obj->count > obj->range) { if (!gCampaign.IsClient) { FireGuns(obj, &obj->bulletClass->OutOfRangeGuns); } return false; } const Vec2i objPos = Vec2iNew(obj->x, obj->y); if (obj->bulletClass->SeekFactor > 0) { // Find the closest target to this bullet and steer towards it const TActor *owner = ActorGetByUID(obj->ActorUID); if (owner == NULL) { return false; } const TActor *target = AIGetClosestEnemy(objPos, owner, obj->flags); if (target && !target->dead) { for (int i = 0; i < ticks; i++) { obj->vel = SeekTowards( objPos, obj->vel, obj->bulletClass->SpeedLow, target->Pos, obj->bulletClass->SeekFactor); } } } Vec2i pos = Vec2iScale(Vec2iAdd(objPos, obj->vel), ticks); HitType hitItem = HIT_NONE; if (!gCampaign.IsClient) { hitItem = HitItem(obj, pos, obj->bulletClass->Persists); } const Vec2i realPos = Vec2iFull2Real(pos); // Falling (grenades) if (obj->bulletClass->Falling.GravityFactor != 0) { bool hasDropped = obj->z <= 0; for (int i = 0; i < ticks; i++) { obj->z += obj->dz; if (obj->z <= 0) { obj->z = 0; if (obj->bulletClass->Falling.Bounces) { obj->dz = -obj->dz / 2; } else { obj->dz = 0; } if (!hasDropped) { if (!gCampaign.IsClient) { FireGuns(obj, &obj->bulletClass->Falling.DropGuns); } } hasDropped = true; if (obj->bulletClass->Falling.DestroyOnDrop) { return false; } SoundPlayAt( &gSoundDevice, StrSound(obj->bulletClass->HitSound.Wall), realPos); } else { obj->dz -= obj->bulletClass->Falling.GravityFactor; } if (!obj->bulletClass->Falling.FallsDown) { obj->dz = MAX(0, obj->dz); } } } // Friction const bool isDiagonal = obj->vel.x != 0 && obj->vel.y != 0; int frictionComponent = isDiagonal ? (int)round(obj->bulletClass->Friction / sqrt(2)) : obj->bulletClass->Friction; for (int i = 0; i < ticks; i++) { if (obj->vel.x > 0) { obj->vel.x -= frictionComponent; } else if (obj->vel.x < 0) { obj->vel.x += frictionComponent; } if (obj->vel.y > 0) { obj->vel.y -= frictionComponent; } else if (obj->vel.y < 0) { obj->vel.y += frictionComponent; } } bool hitWall = false; if (!gCampaign.IsClient && hitItem == HIT_NONE) { hitWall = MapIsRealPosIn(&gMap, realPos) && ShootWall(realPos.x, realPos.y); } if (hitWall || hitItem != HIT_NONE) { GameEvent b = GameEventNew(GAME_EVENT_BULLET_BOUNCE); b.u.BulletBounce.UID = obj->UID; if (hitWall && !Vec2iIsZero(obj->vel)) { b.u.BulletBounce.HitType = (int)HIT_WALL; } else { b.u.BulletBounce.HitType = (int)hitItem; } bool alive = true; if ((hitWall && !obj->bulletClass->WallBounces) || ((hitItem != HIT_NONE) && obj->bulletClass->HitsObjects)) { b.u.BulletBounce.Spark = true; CASSERT(!gCampaign.IsClient, "Cannot process bounces as client"); FireGuns(obj, &obj->bulletClass->HitGuns); if (hitWall || !obj->bulletClass->Persists) { alive = false; } } b.u.BulletBounce.BouncePos = Vec2i2Net(pos); b.u.BulletBounce.BounceVel = Vec2i2Net(obj->vel); if (hitWall && !Vec2iIsZero(obj->vel)) { // Bouncing Vec2i bounceVel = obj->vel; pos = GetWallBounceFullPos(objPos, pos, &bounceVel); b.u.BulletBounce.BouncePos = Vec2i2Net(pos); b.u.BulletBounce.BounceVel = Vec2i2Net(bounceVel); obj->vel = bounceVel; } GameEventsEnqueue(&gGameEvents, b); if (!alive) { return false; } } if (!MapTryMoveTileItem(&gMap, &obj->tileItem, realPos)) { obj->count = obj->range; return false; } obj->x = pos.x; obj->y = pos.y; if (obj->bulletClass->Erratic) { for (int i = 0; i < ticks; i++) { obj->vel.x += ((rand() % 3) - 1) * 128; obj->vel.y += ((rand() % 3) - 1) * 128; } } // Proximity function, destroy // Only check proximity every now and then if (obj->bulletClass->ProximityGuns.size > 0 && !(obj->count & 3)) { if (!gCampaign.IsClient) { // Detonate the mine if there are characters in the tiles around it const Vec2i tv = Vec2iToTile(Vec2iFull2Real(pos)); Vec2i dv; for (dv.y = -1; dv.y <= 1; dv.y++) { for (dv.x = -1; dv.x <= 1; dv.x++) { const Vec2i dtv = Vec2iAdd(tv, dv); if (!MapIsTileIn(&gMap, dtv)) { continue; } if (TileHasCharacter(MapGetTile(&gMap, dtv))) { FireGuns(obj, &obj->bulletClass->ProximityGuns); return false; } } } } } return true; }
static bool DoDamageCharacter( const Vec2i pos, const Vec2i hitVector, const int power, const int flags, const int player, const int uid, const TTileItem *target, const special_damage_e special, const HitSounds *hitSounds, const bool allowFriendlyHitSound) { // Create events: hit, damage, score TActor *actor = CArrayGet(&gActors, target->id); CASSERT(actor->isInUse, "Cannot damage nonexistent player"); bool canHit = CanHitCharacter(flags, uid, actor); if (canHit) { GameEvent e; e.Type = GAME_EVENT_HIT_CHARACTER; e.u.HitCharacter.TargetId = actor->tileItem.id; e.u.HitCharacter.Special = special; GameEventsEnqueue(&gGameEvents, e); if (gConfig.Sound.Hits && hitSounds != NULL && !ActorIsImmune(actor, special) && (allowFriendlyHitSound || !ActorIsInvulnerable( actor, flags, player, gCampaign.Entry.Mode))) { GameEvent es; es.Type = GAME_EVENT_SOUND_AT; es.u.SoundAt.Sound = hitSounds->Flesh; es.u.SoundAt.Pos = pos; GameEventsEnqueue(&gGameEvents, es); } if (gConfig.Game.ShotsPushback) { GameEvent ei; ei.Type = GAME_EVENT_ACTOR_IMPULSE; ei.u.ActorImpulse.Id = actor->tileItem.id; ei.u.ActorImpulse.Vel = Vec2iScaleDiv( Vec2iScale(hitVector, power), SHOT_IMPULSE_DIVISOR); GameEventsEnqueue(&gGameEvents, ei); } if (CanDamageCharacter(flags, player, uid, actor, special)) { GameEvent e1; e1.Type = GAME_EVENT_DAMAGE_CHARACTER; e1.u.DamageCharacter.Power = power; e1.u.DamageCharacter.PlayerIndex = player; e1.u.DamageCharacter.TargetId = actor->tileItem.id; e1.u.DamageCharacter.TargetPlayerIndex = -1; if (actor->pData) { e1.u.DamageCharacter.TargetPlayerIndex = actor->pData->playerIndex; } GameEventsEnqueue(&gGameEvents, e1); if (gConfig.Game.Gore != GORE_NONE) { GameEvent eb; memset(&eb, 0, sizeof eb); eb.Type = GAME_EVENT_ADD_PARTICLE; eb.u.AddParticle.FullPos = Vec2iReal2Full(pos); eb.u.AddParticle.Z = 10 * Z_FACTOR; int bloodPower = power * 2; int bloodSize = 1; while (bloodPower > 0) { switch (bloodSize) { case 1: eb.u.AddParticle.Class = StrParticleClass(&gParticleClasses, "blood1"); break; case 2: eb.u.AddParticle.Class = StrParticleClass(&gParticleClasses, "blood2"); break; default: eb.u.AddParticle.Class = StrParticleClass(&gParticleClasses, "blood3"); break; } bloodSize++; if (bloodSize > 3) { bloodSize = 1; } if (gConfig.Game.ShotsPushback) { eb.u.AddParticle.Vel = Vec2iScaleDiv( Vec2iScale(hitVector, (rand() % 8 + 8) * power), 15 * SHOT_IMPULSE_DIVISOR); } else { eb.u.AddParticle.Vel = Vec2iScaleDiv( Vec2iScale(hitVector, rand() % 8 + 8), 20); } eb.u.AddParticle.Vel.x += (rand() % 128) - 64; eb.u.AddParticle.Vel.y += (rand() % 128) - 64; eb.u.AddParticle.Angle = RAND_DOUBLE(0, PI * 2); eb.u.AddParticle.DZ = (rand() % 6) + 6; eb.u.AddParticle.Spin = RAND_DOUBLE(-0.1, 0.1); GameEventsEnqueue(&gGameEvents, eb); switch (gConfig.Game.Gore) { case GORE_LOW: bloodPower /= 8; break; case GORE_MEDIUM: bloodPower /= 2; break; default: bloodPower = bloodPower * 7 / 8; break; } } } if (player >= 0 && power != 0) { // Calculate score based on // if they hit a penalty character GameEvent e2; e2.Type = GAME_EVENT_SCORE; e2.u.Score.PlayerIndex = player; if (actor->flags & FLAGS_PENALTY) { e2.u.Score.Score = PENALTY_MULTIPLIER * power; } else { e2.u.Score.Score = power; } GameEventsEnqueue(&gGameEvents, e2); } } } return canHit; }