diff --git a/documentation/EditorAndServerBuildNotes.txt b/documentation/EditorAndServerBuildNotes.txt index 441499df7..8af90dc2d 100644 --- a/documentation/EditorAndServerBuildNotes.txt +++ b/documentation/EditorAndServerBuildNotes.txt @@ -121,7 +121,7 @@ echo "69" > serverCodeVersionNumber.txt If you want to write a bash script to do this for you, try this: -git for-each-ref --sort=-creatordate --format '%(refname:short)' --count=1 refs/tags | sed -e 's/OneLife_v//' > serverCodeVersionNumber.txt +git for-each-ref --sort=-creatordate --format '%(refname:short)' --count=2 refs/tags | grep "OneLife_v" | sed -e 's/OneLife_v//' > serverCodeVersionNumber.txt diff --git a/documentation/changeLog.txt b/documentation/changeLog.txt index 0b35ba43d..765fa08f3 100644 --- a/documentation/changeLog.txt +++ b/documentation/changeLog.txt @@ -3,6 +3,86 @@ This file only list changes to game code. Changes to content can be found here: http://onehouronelife.com/updateLog.php +Version 231 2019-May-23 + +--Tiles that are drawn as non-flipped remember their non-flipped status, even + if they change to flippable tiles later (so they don't flip back and forth + in an unappealing way). + +--Client supports new KILL message semantics. + +--Fixed a glitch where other players showed doing animation when their held + object passively changed. + +--Fixed glitch on auto-click at the end of very short paths. + +--Applied fixes for various movement glitches. Thanks Justin L. + +--KILL action now requires SHIFT key held. Instruction image updated. + + + + +Server Fixes: + +--Fixed bug in deletion of underlying object if grave marker has no slots. + +--Increased name match limit in family tree search to 30,000, and ensuring + performance by forcing use of name column as index in all name queries. + +--Code that accepts KILL command is more liberal in case of a moving target. + Now it's very hard to miss with the bow, assuming that the target is within + range and you actually click on them client-side. + +--Fixed so that use-on-bare-ground objects that are groundOnly can't be used on + floors. + +--Added a script to automate end-user installation of a private server on Linux. + +--Fixed so that Eves don't spawn near tutorial town. + +--Fixed so that VOG doesn't see language-mapped phrases. + +--Fixed so that spring placement not blocked by objects to the south. + +--Improved thermal model for buildings. Now using a 13x13 heat grid instead of + 8x8, to support larger buildings. We now detect a binary indoor/outdoor + property for each player, based on being completely surrounded by walls, + floors, and closed doors. If indoors, the effect of a cold environment is + halved, and the effect of clothing to protect from cold environments is + doubled. + +--Changed semantics of player KILL action server-side. Instead of an + instantaneous action based on player proximity that either succeeds or fails, + the server remembers that the player is trying to kill that other player and + forces an angry emote for their face. If they ever get within range of the + target player, the kill action is executed. The kill state will get canceled + if they drop their weapon, target someone else, or if their target dies of + other causes. Essentially, once you set a quest for blood on another player, + if you ever cross paths with them, it will happen. This dramatically reduces + the effectiveness of dancing around, but doesn't undercut the advantage of + range weapons (you can still run away from an attacker). If two players + attempt to kill each other simultaneously, the player that was put into the + kill state first will succeed, if the players ever cross paths. + +--Fixed missing fseek between fread and fwrite calls. Thanks Justin L. + Related to pull request #268 + +--Fixed bug in param order in call to getClosestOtherPlayer. + +--If you didn't inherit a family name at birth, you get the family name of + whoever is holding you when they name you. Related to #227 + +--Standardized other-person-naming code, whether for held baby, or non-held, + currently-nameless adult. Baby inherits the family name of their mother at + birth, or the family name of their namer otherwise. Family name can differ + from last name, depending on who named you, but your family name will + resurface later down the line as a last name if your last name is lost. + Fixes #277 + + + + Version 228 2019-May-16 --Abbreviating more than 4 GREATS in grave names as 5X GREAT so that it doesn't diff --git a/gameSource/LivingLifePage.cpp b/gameSource/LivingLifePage.cpp index 047f89791..65e1f7638 100644 --- a/gameSource/LivingLifePage.cpp +++ b/gameSource/LivingLifePage.cpp @@ -1551,6 +1551,61 @@ void updateMoveSpeed( LiveObject *inObject ) { } + +static void fixSingleStepPath( LiveObject *inObject ) { + + printf( "Fix for overtruncated, single-step path for player %d\n", + inObject->id ); + + // trimmed path too short + // needs to have at least + // a start and end pos + + // give it an artificial + // start pos + + + doublePair nextWorld = + gridToDouble( + inObject->pathToDest[0] ); + + + doublePair vectorAway; + + if( ! equal( + inObject->currentPos, + nextWorld ) ) { + + vectorAway = normalize( + sub( + inObject-> + currentPos, + nextWorld ) ); + } + else { + vectorAway.x = 1; + vectorAway.y = 0; + } + + GridPos oldPos = + inObject->pathToDest[0]; + + delete [] inObject->pathToDest; + inObject->pathLength = 2; + + inObject->pathToDest = + new GridPos[2]; + inObject->pathToDest[0].x = + oldPos.x + vectorAway.x; + inObject->pathToDest[0].y = + oldPos.y + vectorAway.y; + + inObject->pathToDest[1] = + oldPos; + } + + + // should match limit on server static int pathFindingD = 32; @@ -3399,6 +3454,10 @@ void LivingLifePage::drawMapCell( int inMapI, // or permanent behind-player objects (e.g., roads) // are never drawn flipped flip = false; + // remember that this tile is NOT flipped, so that it + // won't flip back strangely if it changes to something + // that doesn't have a noFlip status + mMapTileFlips[ inMapI ] = false; } char highlight = false; @@ -10901,7 +10960,6 @@ void LivingLifePage::step() { // end it ourObject->pendingActionAnimationProgress = 0; - ourObject->pendingActionAnimationTotalProgress = 0; ourObject->pendingAction = false; playerActionPending = false; @@ -12951,6 +13009,9 @@ void LivingLifePage::step() { o.currentEmot = NULL; o.emotClearETATime = 0; + o.killMode = false; + o.killWithID = -1; + int forced = 0; int done_moving = 0; @@ -13352,6 +13413,10 @@ void LivingLifePage::step() { break; } } + + if( existing->pathLength == 1 ) { + fixSingleStepPath( existing ); + } } } @@ -13471,6 +13536,14 @@ void LivingLifePage::step() { existing->xd = o.xd; existing->yd = o.yd; existing->destTruncated = false; + + // clear an existing path, since they may no + // longer be on it + if( existing->pathToDest != NULL ) { + delete [] existing->pathToDest; + existing->pathToDest = NULL; + } + } existing->outOfRange = false; @@ -13561,8 +13634,6 @@ void LivingLifePage::step() { existing->actionTargetY = actionTargetY; existing->pendingActionAnimationProgress = 0.025 * frameRateFactor; - existing->pendingActionAnimationTotalProgress = - existing->pendingActionAnimationProgress; } if( heldOriginValid || @@ -13584,7 +13655,38 @@ void LivingLifePage::step() { } } } + else if( o.id == ourID && + actionAttempt && + ! justAte && + existing->killMode && + // they were still holding the same weapon + // before this update + existing->killWithID == oldHeld && + // their weapon changed as a result of this + // update + existing->holdingID != oldHeld ) { + + // show kill "doing" animation and bounce + + playerActionTargetX = actionTargetX; + playerActionTargetY = actionTargetY; + + addNewAnimPlayerOnly( existing, doing ); + if( existing->pendingActionAnimationProgress + == 0 ) { + existing->pendingActionAnimationProgress = + 0.025 * frameRateFactor; + } + + if( facingOverride == 1 ) { + existing->holdingFlip = false; + } + else if( facingOverride == -1 ) { + existing->holdingFlip = true; + } + } + char creationSoundPlayed = false; char otherSoundPlayed = false; @@ -13747,7 +13849,8 @@ void LivingLifePage::step() { } else { // don't interrupt walking - if( nearEndOfMovement( existing ) ) { + if( actionAttempt && + nearEndOfMovement( existing ) ) { addNewAnimPlayerOnly( existing, doing ); } @@ -14303,6 +14406,13 @@ void LivingLifePage::step() { existing->yd = o.yd; existing->destTruncated = false; + // clear an existing path, since they may no + // longer be on it + if( existing->pathToDest != NULL ) { + delete [] existing->pathToDest; + existing->pathToDest = NULL; + } + if( existing->lastHeldByRawPosSet ) { existing->heldByDropOffset = sub( existing->lastHeldByRawPos, @@ -14395,8 +14505,6 @@ void LivingLifePage::step() { if( forced ) { existing->pendingActionAnimationProgress = 0; - existing->pendingActionAnimationTotalProgress = - 0; existing->pendingAction = false; playerActionPending = false; @@ -14526,7 +14634,6 @@ void LivingLifePage::step() { o.pendingAction = false; o.pendingActionAnimationProgress = 0; - o.pendingActionAnimationTotalProgress = 0; o.currentPos.x = o.xd; o.currentPos.y = o.yd; @@ -15326,52 +15433,7 @@ void LivingLifePage::step() { existing->pathLength ); if( existing->pathLength == 1 ) { - - // trimmed path too short - // needs to have at least - // a start and end pos - - // give it an artificial - // start pos - - - doublePair nextWorld = - gridToDouble( - existing->pathToDest[0] ); - - - doublePair vectorAway; - - if( ! equal( - existing->currentPos, - nextWorld ) ) { - - vectorAway = normalize( - sub( - existing-> - currentPos, - nextWorld ) ); - } - else { - vectorAway.x = 1; - vectorAway.y = 0; - } - - GridPos oldPos = - existing->pathToDest[0]; - - delete [] existing->pathToDest; - existing->pathLength = 2; - - existing->pathToDest = - new GridPos[2]; - existing->pathToDest[0].x = - oldPos.x + vectorAway.x; - existing->pathToDest[0].y = - oldPos.y + vectorAway.y; - - existing->pathToDest[1] = - oldPos; + fixSingleStepPath( existing ); } @@ -15486,8 +15548,6 @@ void LivingLifePage::step() { // (no longer possible, since truncated) existing->pendingActionAnimationProgress = 0; - existing->pendingActionAnimationTotalProgress = - 0; existing->pendingAction = false; playerActionPending = false; @@ -16997,7 +17057,9 @@ void LivingLifePage::step() { } else { - if( o->id == ourID && mouseDown && shouldMoveCamera ) { + if( o->id == ourID && mouseDown && shouldMoveCamera && + o->pathLength > 2 ) { + float worldMouseX, worldMouseY; screenToWorld( lastScreenMouseX, @@ -17064,11 +17126,17 @@ void LivingLifePage::step() { o->waypointY = lrint( worldMouseY / CELL_D ); pointerDown( fakeClick.x, fakeClick.y ); + + endPos.x = (double)( fakeClick.x ); + endPos.y = (double)( fakeClick.y ); o->useWaypoint = false; } else { pointerDown( worldMouseX, worldMouseY ); + + endPos.x = (double)( worldMouseX ); + endPos.y = (double)( worldMouseY ); } } } @@ -17265,7 +17333,6 @@ void LivingLifePage::step() { ( o->pendingAction || o->pendingActionAnimationProgress != 0 ) ) { o->pendingActionAnimationProgress += progressInc; - o->pendingActionAnimationTotalProgress += progressInc; if( o->pendingActionAnimationProgress > 1 ) { if( o->pendingAction ) { @@ -17276,7 +17343,6 @@ void LivingLifePage::step() { // no longer pending, finish last cycle by snapping // back to 0 o->pendingActionAnimationProgress = 0; - o->pendingActionAnimationTotalProgress = 0; o->actionTargetTweakX = 0; o->actionTargetTweakY = 0; } @@ -17285,13 +17351,11 @@ void LivingLifePage::step() { else if( o->id != ourID && o->pendingActionAnimationProgress != 0 ) { o->pendingActionAnimationProgress += progressInc; - o->pendingActionAnimationTotalProgress += progressInc; if( o->pendingActionAnimationProgress > 1 ) { // no longer pending, finish last cycle by snapping // back to 0 o->pendingActionAnimationProgress = 0; - o->pendingActionAnimationTotalProgress = 0; o->actionTargetTweakX = 0; o->actionTargetTweakY = 0; } @@ -17346,9 +17410,7 @@ void LivingLifePage::step() { // matter how fast the server responds ourLiveObject->pendingActionAnimationProgress = 0.025 * frameRateFactor; - ourLiveObject->pendingActionAnimationTotalProgress = - ourLiveObject->pendingActionAnimationProgress; - + ourLiveObject->pendingActionAnimationStartTime = currentTime; @@ -19206,108 +19268,74 @@ void LivingLifePage::pointerDown( float inX, float inY ) { } - // true if we're too far away to kill BUT we should execute - // kill once we get to destination - // if we're close enough to kill, we'll kill from where we're standing - // and return - char killLater = false; - int killLaterID = -1; + // new semantics + // as soon as we trigger a kill attempt, we go into kill mode + // by sending server a KILL message right away + char killMode = false; if( destID == 0 && + p.hitOtherPerson && modClick && ourLiveObject->holdingID > 0 && - getObject( ourLiveObject->holdingID )->deadlyDistance > 0 ) { + getObject( ourLiveObject->holdingID )->deadlyDistance > 0 && + isShiftKeyDown() ) { // special case // check for possible kill attempt at a distance - // if it fails (target too far away or no person near), - // then we resort to standard drop code below - - - double d = sqrt( ( clickDestX - ourLiveObject->xd ) * - ( clickDestX - ourLiveObject->xd ) - + - ( clickDestY - ourLiveObject->yd ) * - ( clickDestY - ourLiveObject->yd ) ); - doublePair targetPos = { (double)clickDestX, (double)clickDestY }; - - - for( int i=0; iid != ourID && - if( o->id != ourID && - o->heldByAdultID == -1 ) { + o->heldByAdultID == -1 ) { - // can't kill by clicking on ghost-location of held baby - - if( distance( targetPos, o->currentPos ) < 1 ) { - // clicked on someone + // can't kill by clicking on ghost-location of held baby + + if( distance( targetPos, o->currentPos ) < 1 ) { + // clicked on someone - if( getObject( ourLiveObject->holdingID )->deadlyDistance - >= d ) { - // close enough to use deadly object right now - - - if( nextActionMessageToSend != NULL ) { - delete [] nextActionMessageToSend; - nextActionMessageToSend = NULL; - } - - if( p.hitOtherPerson ) { - nextActionMessageToSend = - autoSprintf( "KILL %d %d %d#", - sendX( clickDestX ), - sendY( clickDestY ), - p.hitOtherPersonID ); - printf( "KILL with direct-click target player " - "id=%d\n", p.hitOtherPersonID ); - } - else { - nextActionMessageToSend = - autoSprintf( "KILL %d %d#", - sendX( clickDestX ), - sendY( clickDestY ) ); - printf( "KILL with target " - "player on closest tile, id=%d\n", o->id ); - } - - - playerActionTargetX = clickDestX; - playerActionTargetY = clickDestY; - - playerActionTargetNotAdjacent = true; + // new semantics: + // send KILL to server right away to + // tell server of our intentions + // (whether or not we are close enough) + + // then walk there + + if( nextActionMessageToSend != NULL ) { + delete [] nextActionMessageToSend; + nextActionMessageToSend = NULL; + } + char *killMessage = + autoSprintf( "KILL %d %d %d#", + sendX( clickDestX ), + sendY( clickDestY ), + p.hitOtherPersonID ); + printf( "KILL with direct-click target player " + "id=%d\n", p.hitOtherPersonID ); + + sendToServerSocket( killMessage ); - return; - } - else { - // too far away, but try to kill later, - // once we walk there, using standard path-to-adjacent - // code below - killLater = true; - - if( p.hitOtherPerson ) { - killLaterID = p.hitOtherPersonID; - } - - break; - } - } + // try to walk near victim right away + killMode = true; + + ourLiveObject->killMode = true; + ourLiveObject->killWithID = ourLiveObject->holdingID; + + // ignore mod-click from here on out, to avoid + // force-dropping weapon + modClick = false; } } - } - if( ! killLater && - destID != 0 && + if( destID != 0 && ! modClick && ourLiveObject->holdingID > 0 && getObject( ourLiveObject->holdingID )->useDistance > 1 && @@ -19395,8 +19423,7 @@ void LivingLifePage::pointerDown( float inX, float inY ) { // and return char useOnBabyLater = false; - if( !killLater && - p.hitOtherPerson && + if( p.hitOtherPerson && ! modClick && destID == 0 && canClickOnOtherForNonKill ) { @@ -19501,7 +19528,8 @@ void LivingLifePage::pointerDown( float inX, float inY ) { - if( destID == 0 && !modClick && !tryingToPickUpBaby && !useOnBabyLater && + if( !killMode && + destID == 0 && !modClick && !tryingToPickUpBaby && !useOnBabyLater && ! ( clickDestX == ourLiveObject->xd && clickDestY == ourLiveObject->yd ) ) { // a move to an empty spot where we're not already standing @@ -19510,6 +19538,7 @@ void LivingLifePage::pointerDown( float inX, float inY ) { mustMove = true; } else if( ( modClick && ourLiveObject->holdingID != 0 ) + || killMode || tryingToPickUpBaby || useOnBabyLater || destID != 0 @@ -19669,7 +19698,7 @@ void LivingLifePage::pointerDown( float inX, float inY ) { } - if( canExecute ) { + if( canExecute && ! killMode ) { const char *action = ""; char *extra = stringDuplicate( "" ); @@ -19697,61 +19726,46 @@ void LivingLifePage::pointerDown( float inX, float inY ) { nextActionDropping = true; send = true; - // special case: we're too far away to kill someone - // but we've right clicked on them from a distance - // walk up and execute KILL once we get there. - - if( killLater ) { - action = "KILL"; + // check for other special case + // a use-on-ground transition or use-on-floor transition - if( killLaterID != -1 ) { - delete [] extra; - extra = autoSprintf( " %d", killLaterID ); - } - } - else { - // check for other special case - // a use-on-ground transition or use-on-floor transition - - if( ourLiveObject->holdingID > 0 ) { + if( ourLiveObject->holdingID > 0 ) { - ObjectRecord *held = - getObject( ourLiveObject->holdingID ); + ObjectRecord *held = + getObject( ourLiveObject->holdingID ); - char foundAlt = false; + char foundAlt = false; - if( held->foodValue == 0 ) { + if( held->foodValue == 0 ) { - TransRecord *r = - getTrans( ourLiveObject->holdingID, - -1 ); + TransRecord *r = + getTrans( ourLiveObject->holdingID, + -1 ); - if( r != NULL && - r->newTarget != 0 ) { + if( r != NULL && + r->newTarget != 0 ) { - // a use-on-ground transition exists! + // a use-on-ground transition exists! - // override the drop action - action = "USE"; - foundAlt = true; - } + // override the drop action + action = "USE"; + foundAlt = true; } + } - if( !foundAlt && floorDestID > 0 ) { - // check if use on floor exists - TransRecord *r = - getTrans( ourLiveObject->holdingID, - floorDestID ); + if( !foundAlt && floorDestID > 0 ) { + // check if use on floor exists + TransRecord *r = + getTrans( ourLiveObject->holdingID, + floorDestID ); - if( r != NULL ) { - // a use-on-floor transition exists! + if( r != NULL ) { + // a use-on-floor transition exists! - // override the drop action - action = "USE"; + // override the drop action + action = "USE"; - } } - } } } diff --git a/gameSource/LivingLifePage.h b/gameSource/LivingLifePage.h index 496281ba3..8f1772bfe 100644 --- a/gameSource/LivingLifePage.h +++ b/gameSource/LivingLifePage.h @@ -267,7 +267,6 @@ typedef struct LiveObject { char pendingAction; float pendingActionAnimationProgress; - float pendingActionAnimationTotalProgress; double pendingActionAnimationStartTime; double lastActionSendStartTime; @@ -300,6 +299,9 @@ typedef struct LiveObject { // wall clock time when emot clears double emotClearETATime; + char killMode; + int killWithID; + } LiveObject; diff --git a/gameSource/game.cpp b/gameSource/game.cpp index fd78c5edb..d22b186f0 100644 --- a/gameSource/game.cpp +++ b/gameSource/game.cpp @@ -1,4 +1,4 @@ -int versionNumber = 228; +int versionNumber = 231; int dataVersionNumber = 0; int binVersionNumber = versionNumber; diff --git a/gameSource/graphics/instructions.tga b/gameSource/graphics/instructions.tga index f90e3aa9f..e132b4c53 100644 Binary files a/gameSource/graphics/instructions.tga and b/gameSource/graphics/instructions.tga differ diff --git a/gameSource/graphicsSource/instructions.png b/gameSource/graphicsSource/instructions.png index b2fe4844d..dbd054de5 100644 Binary files a/gameSource/graphicsSource/instructions.png and b/gameSource/graphicsSource/instructions.png differ diff --git a/lineageServer/server.php b/lineageServer/server.php index 050a9fe0b..241f6f93f 100644 --- a/lineageServer/server.php +++ b/lineageServer/server.php @@ -1572,6 +1572,9 @@ function ls_frontPage() { $tooManyNameMatches = false; $numNameMatches = 0; + + $forceIndexClause = ""; + if( $email_sha1 != "" ) { @@ -1606,10 +1609,18 @@ function ls_frontPage() { $result = ls_queryDatabase( $query ); $numNameMatches = ls_mysqli_result( $result, 0, 0 ); - if( $numNameMatches > 1000 ) { + if( $numNameMatches > 30000 ) { $filterClause = " WHERE 1 "; $tooManyNameMatches = true; } + else { + // not too many + // force index on name to keep it fast + // (otherwise, we sort by another criteria on front page + // and may only find name matches at bottom of list of millions) + $forceIndexClause = " FORCE INDEX( name ) "; + } + } @@ -1661,7 +1672,8 @@ function ls_frontPage() { echo "". "Recent Elder Deaths:\n"; - ls_printFrontPageRows( "$filterClause AND age >= 50", "death_time DESC", + ls_printFrontPageRows( $forceIndexClause, + "$filterClause AND age >= 50", "death_time DESC", $numPerList ); @@ -1669,6 +1681,7 @@ function ls_frontPage() { "\n"; ls_printFrontPageRows( + $forceIndexClause, "$rootFilterClause AND death_time >= DATE_SUB( NOW(), INTERVAL 1 DAY )", "lineage_depth DESC, death_time DESC", $numPerList ); @@ -1677,7 +1690,8 @@ function ls_frontPage() { echo "". "Recent Adult Deaths:\n"; - ls_printFrontPageRows( "$filterClause AND age >= 20 AND age < 50", + ls_printFrontPageRows( $forceIndexClause, + "$filterClause AND age >= 20 AND age < 50", "death_time DESC", $numPerList ); @@ -1685,7 +1699,8 @@ function ls_frontPage() { echo "". "Recent Youth Deaths:\n"; - ls_printFrontPageRows( "$filterClause AND age < 20", "death_time DESC", + ls_printFrontPageRows( $forceIndexClause, + "$filterClause AND age < 20", "death_time DESC", $numPerList ); @@ -1694,6 +1709,7 @@ function ls_frontPage() { "\n"; ls_printFrontPageRows( + $forceIndexClause, "$rootFilterClause AND ". "death_time >= DATE_SUB( NOW(), INTERVAL 1 WEEK )", "lineage_depth DESC, death_time DESC", @@ -1704,6 +1720,7 @@ function ls_frontPage() { "\n"; ls_printFrontPageRows( + $forceIndexClause, $rootFilterClause, "lineage_depth DESC, death_time DESC", $numPerList ); @@ -1714,6 +1731,7 @@ function ls_frontPage() { "\n"; ls_printFrontPageRows( + $forceIndexClause, "$filterClause AND death_time >= DATE_SUB( NOW(), INTERVAL 1 DAY )", "generation DESC, death_time DESC", $numPerList ); @@ -1723,6 +1741,7 @@ function ls_frontPage() { "\n"; ls_printFrontPageRows( + $forceIndexClause, "$filterClause AND death_time >= DATE_SUB( NOW(), INTERVAL 1 WEEK )", "generation DESC, death_time DESC", $numPerList ); @@ -1732,7 +1751,8 @@ function ls_frontPage() { echo "All-Time Long Lines:". "\n"; - ls_printFrontPageRows( $filterClause, "generation DESC, death_time DESC", + ls_printFrontPageRows( $forceIndexClause, + $filterClause, "generation DESC, death_time DESC", $numPerList ); @@ -1761,7 +1781,8 @@ function ls_getGrayPercent( $inDeathAgoSec ) { -function ls_printFrontPageRows( $inFilterClause, $inOrderBy, $inNumRows ) { +function ls_printFrontPageRows( $inForceIndexClause, + $inFilterClause, $inOrderBy, $inNumRows ) { global $tableNamePrefix; global $photoServerURL, $usePhotoServer; @@ -1770,6 +1791,7 @@ function ls_printFrontPageRows( $inFilterClause, $inOrderBy, $inNumRows ) { "age, generation, death_time, deepest_descendant_generation, ". "servers.server " . "FROM $tableNamePrefix"."lives as lives ". + " $inForceIndexClause ". "INNER JOIN $tableNamePrefix"."users as users ". "ON lives.user_id = users.id ". "INNER JOIN $tableNamePrefix"."servers as servers ". diff --git a/server/installYourOwnServer/Readme.txt b/server/installYourOwnServer/Readme.txt new file mode 100644 index 000000000..86caef89d --- /dev/null +++ b/server/installYourOwnServer/Readme.txt @@ -0,0 +1,23 @@ +This script will automate the process of git-pulling and installing your own +server on a GNU/Linux machine. + +Make a folder for it first, like this: + +mkdir myServer + +Make sure you have read/write permissions to this folder, assuming that your +user account will be running the server. If the server is going to be run +by some other account instead, make sure THAT account has read-write +permissions. + + +Then copy this script into that folder. Suppose myServer is in your home +directory: + +cp serverPullAndBuild.sh ~/myServer + + +Now change to that directory and run the script: + +cd ~/myServer +bash serverPullAndBuild.sh diff --git a/server/installYourOwnServer/serverPullAndBuildLinux.sh b/server/installYourOwnServer/serverPullAndBuildLinux.sh new file mode 100755 index 000000000..338be16ca --- /dev/null +++ b/server/installYourOwnServer/serverPullAndBuildLinux.sh @@ -0,0 +1,63 @@ +#!/bin/bash + + +git clone https://github.com/jasonrohrer/minorGems.git +git clone https://github.com/jasonrohrer/OneLife.git +git clone https://github.com/jasonrohrer/OneLifeData7.git + + + + +cd minorGems +git fetch --tags +latestTaggedVersion=`git for-each-ref --sort=-creatordate --format '%(refname:short)' --count=1 refs/tags/OneLife_v* | sed -e 's/OneLife_v//'` +git checkout -q OneLife_v$latestTaggedVersion + + +cd ../OneLife +git fetch --tags +latestTaggedVersionA=`git for-each-ref --sort=-creatordate --format '%(refname:short)' --count=1 refs/tags/OneLife_v* | sed -e 's/OneLife_v//'` +git checkout -q OneLife_v$latestTaggedVersionA + + +cd ../OneLifeData7 +git fetch --tags +latestTaggedVersionB=`git for-each-ref --sort=-creatordate --format '%(refname:short)' --count=1 refs/tags/OneLife_v* | sed -e 's/OneLife_v//'` +git checkout -q OneLife_v$latestTaggedVersionB + + +cd .. + +cd OneLife +git pull --tags +git checkout OneLife_liveServer +cd .. + + +cd OneLife/server +./configure 1 +make +ln -s ../../OneLifeData7/objects . +ln -s ../../OneLifeData7/transitions . +ln -s ../../OneLifeData7/categories . +ln -s ../../OneLifeData7/tutorialMaps . +ln -s ../../OneLifeData7/dataVersionNumber.txt . + +git for-each-ref --sort=-creatordate --format '%(refname:short)' --count=2 refs/tags | grep "OneLife_v" | sed -e 's/OneLife_v//' > serverCodeVersionNumber.txt + + +serverVersion=`cat serverCodeVersionNumber.txt` + +echo +echo +echo "Done building server with version v$serverVersion" + + +echo +echo "To run your server, do this:" +echo +echo "cd OneLife/server" +echo "./OneLifeServer" +echo + + diff --git a/server/lineardb3.cpp b/server/lineardb3.cpp index 03307d2c2..cbf0c7f9c 100644 --- a/server/lineardb3.cpp +++ b/server/lineardb3.cpp @@ -481,7 +481,8 @@ int LINEARDB3_open( if( writeHeader( inDB ) != 0 ) { return 1; } - + + inDB->lastOp = opWrite; initPageManager( inDB->hashTable, inDB->hashTableSizeA ); initPageManager( inDB->overflowBuckets, 2 ); @@ -690,6 +691,8 @@ int LINEARDB3_open( return 1; } } + + inDB->lastOp = opRead; } @@ -1025,7 +1028,9 @@ static int LINEARDB3_considerFingerprintBucket( LINEARDB3 *inDB, // read key to make sure it actually matches // never seek unless we have to - if( ftello( inDB->file ) != (signed)filePosRec ) { + if( inDB->lastOp == opWrite || + ftello( inDB->file ) != (signed)filePosRec ) { + if( fseeko( inDB->file, filePosRec, SEEK_SET ) ) { return -1; } @@ -1033,7 +1038,8 @@ static int LINEARDB3_considerFingerprintBucket( LINEARDB3 *inDB, int numRead = fread( inDB->recordBuffer, inDB->keySize, 1, inDB->file ); - + inDB->lastOp = opRead; + if( numRead != 1 ) { return -1; } @@ -1052,7 +1058,8 @@ static int LINEARDB3_considerFingerprintBucket( LINEARDB3 *inDB, // if we're doing a series of fresh inserts, // the file pos is already waiting at the end of the file // for us - if( ftello( inDB->file ) != (signed)filePosRec ) { + if( inDB->lastOp == opRead || + ftello( inDB->file ) != (signed)filePosRec ) { // no seeking done yet // go to end of file @@ -1070,6 +1077,8 @@ static int LINEARDB3_considerFingerprintBucket( LINEARDB3 *inDB, int numWritten = fwrite( inKey, inDB->keySize, 1, inDB->file ); + inDB->lastOp = opWrite; + if( numWritten != 1 ) { return -1; } @@ -1077,9 +1086,15 @@ static int LINEARDB3_considerFingerprintBucket( LINEARDB3 *inDB, // else already seeked and read key of non-empty record // ready to write value + + // still need to seek here after reading before writing + // according to fopen docs + fseeko( inDB->file, 0, SEEK_CUR ); + int numWritten = fwrite( inOutValue, inDB->valueSize, 1, inDB->file ); - + inDB->lastOp = opWrite; + if( numWritten != 1 ) { return -1; } @@ -1095,6 +1110,7 @@ static int LINEARDB3_considerFingerprintBucket( LINEARDB3 *inDB, int numRead = fread( inOutValue, inDB->valueSize, 1, inDB->file ); + inDB->lastOp = opRead; if( numRead != 1 ) { return -1; @@ -1215,7 +1231,8 @@ int LINEARDB3_getOrPut( LINEARDB3 *inDB, const void *inKey, void *inOutValue, inDB->recordSizeBytes; // don't seek unless we have to - if( ftello( inDB->file ) != (signed)filePosRec ) { + if( inDB->lastOp == opRead || + ftello( inDB->file ) != (signed)filePosRec ) { // go to end of file if( fseeko( inDB->file, 0, SEEK_END ) ) { @@ -1231,7 +1248,8 @@ int LINEARDB3_getOrPut( LINEARDB3 *inDB, const void *inKey, void *inOutValue, int numWritten = fwrite( inKey, inDB->keySize, 1, inDB->file ); - + inDB->lastOp = opWrite; + numWritten += fwrite( inOutValue, inDB->valueSize, 1, inDB->file ); if( numWritten != 2 ) { @@ -1307,7 +1325,9 @@ int LINEARDB3_Iterator_next( LINEARDB3_Iterator *inDBi, inDBi->nextRecordIndex * db->recordSizeBytes; - if( ftello( db->file ) != (signed)fileRecPos ) { + if( db->lastOp == opWrite || + ftello( db->file ) != (signed)fileRecPos ) { + if( fseeko( db->file, fileRecPos, SEEK_SET ) ) { return -1; } @@ -1317,6 +1337,8 @@ int LINEARDB3_Iterator_next( LINEARDB3_Iterator *inDBi, int numRead = fread( outKey, db->keySize, 1, db->file ); + db->lastOp = opRead; + if( numRead != 1 ) { return -1; } diff --git a/server/lineardb3.h b/server/lineardb3.h index e24e7ac21..caad4c5ef 100644 --- a/server/lineardb3.h +++ b/server/lineardb3.h @@ -56,7 +56,7 @@ typedef struct { - +enum LastFileOp{ opRead, opWrite }; typedef struct { @@ -83,6 +83,9 @@ typedef struct { FILE *file; + // for deciding when fseek is needed between reads and writes + LastFileOp lastOp; + // equal to the largest possible 32-bit table size, given // our current table size // used as mod for computing 32-bit hash fingerprints diff --git a/server/map.cpp b/server/map.cpp index 6df66d832..ba3d8e403 100644 --- a/server/map.cpp +++ b/server/map.cpp @@ -1010,7 +1010,7 @@ static void mapCacheInsert( int inX, int inY, int inID ) { static int getBaseMapCallCount = 0; -static int getBaseMap( int inX, int inY ) { +static int getBaseMap( int inX, int inY, char *outGridPlacement = NULL ) { if( inX > xLimit || inX < -xLimit || inY > yLimit || inY < -yLimit ) { @@ -1027,6 +1027,11 @@ static int getBaseMap( int inX, int inY ) { getBaseMapCallCount ++; + if( outGridPlacement != NULL ) { + *outGridPlacement = false; + } + + // see if any of our grids apply setXYRandomSeed( 9753 ); @@ -1068,6 +1073,10 @@ static int getBaseMap( int inX, int inY ) { if( gp->permittedBiomes.getElementIndex( pickedBiome ) != -1 ) { mapCacheInsert( inX, inY, gp->id ); + + if( outGridPlacement != NULL ) { + *outGridPlacement = true; + } return gp->id; } } @@ -5135,8 +5144,10 @@ int getMapObjectRaw( int inX, int inY ) { if( result == -1 ) { // nothing in map - result = getBaseMap( inX, inY ); + char wasGridPlacement = false; + result = getBaseMap( inX, inY, &wasGridPlacement ); + if( result > 0 ) { ObjectRecord *o = getObject( result ); @@ -5179,8 +5190,10 @@ int getMapObjectRaw( int inX, int inY ) { } } } - else if( getObjectHeight( result ) < CELL_D ) { + else if( !wasGridPlacement && getObjectHeight( result ) < CELL_D ) { // a short object should be here + // and it wasn't forced by a grid placement + // make sure there's not any semi-short objects below already // this avoids vertical stacking of short objects diff --git a/server/server.cpp b/server/server.cpp index c138b4650..3b2885b7c 100644 --- a/server/server.cpp +++ b/server/server.cpp @@ -68,7 +68,7 @@ static JenkinsRandomSource randSource; #include "../gameSource/GridPos.h" -#define HEAT_MAP_D 8 +#define HEAT_MAP_D 13 float targetHeat = 10; @@ -167,6 +167,8 @@ static char allowedSayCharMap[256]; static const char *allowedSayChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ.-,'?! "; +static int killEmotionIndex = 2; + // for incoming socket connections that are still in the login process @@ -440,6 +442,10 @@ typedef struct LiveObject { // a heat update double lastHeatUpdate; + // true if heat map features player surrounded by walls + char isIndoors; + + int foodStore; @@ -2518,6 +2524,10 @@ static void recomputeHeatMap( LiveObject *inPlayer ) { int gridSize = HEAT_MAP_D * HEAT_MAP_D; + // assume indoors until we find an air boundary of space + inPlayer->isIndoors = true; + + // what if we recompute it from scratch every time? for( int i=0; iheatMap[i] = 0; @@ -2767,6 +2777,7 @@ static void recomputeHeatMap( LiveObject *inPlayer ) { // assume air R-value rBoundarySum += rAir; rBoundarySize ++; + inPlayer->isIndoors = false; } } } @@ -2788,6 +2799,10 @@ static void recomputeHeatMap( LiveObject *inPlayer ) { if( rFloorGrid[i] > rAir ) { numFloorTilesInAirspace++; } + else { + // gap in floor + inPlayer->isIndoors = false; + } } } } @@ -5125,6 +5140,7 @@ int processLoggedInPlayer( Socket *inSock, newObject.heat = 0.5; newObject.heatUpdate = false; newObject.lastHeatUpdate = Time::getCurrentTime(); + newObject.isIndoors = false; newObject.foodDecrementETASeconds = @@ -5439,7 +5455,9 @@ int processLoggedInPlayer( Socket *inSock, LiveObject *player = players.getElement( i ); if( player->error || - ! player->connected ) { + ! player->connected || + player->isTutorial || + player->vogMode ) { continue; } GridPos p = { player->xs, player->ys }; @@ -7646,6 +7664,542 @@ typedef struct FlightDest { +typedef struct KillState { + int killerID; + int killerWeaponID; + int targetID; + double emotStartTime; + } KillState; + + +SimpleVector activeKillStates; + + +void addKillState( int inKillerID, int inTargetID ) { + char found = false; + + for( int i=0; ikillerID == inKillerID ) { + found = true; + s->killerWeaponID = getLiveObject( inKillerID )->holdingID; + s->targetID = inTargetID; + s->emotStartTime = Time::getCurrentTime(); + } + } + + if( !found ) { + // add new + KillState s = { inKillerID, + getLiveObject( inKillerID )->holdingID, + inTargetID, + Time::getCurrentTime() }; + activeKillStates.push_back( s ); + } + } + + + + + +void executeKillAction( int inKillerIndex, + int inTargetIndex, + SimpleVector *playerIndicesToSendUpdatesAbout, + SimpleVector *playerIndicesToSendDyingAbout, + SimpleVector *newEmotPlayerIDs, + SimpleVector *newEmotIndices, + SimpleVector *newEmotTTLs ) { + int i = inKillerIndex; + LiveObject *nextPlayer = players.getElement( inKillerIndex ); + + LiveObject *hitPlayer = players.getElement( inTargetIndex ); + + GridPos targetPos = getPlayerPos( hitPlayer ); + + + // send update even if action fails (to let them + // know that action is over) + playerIndicesToSendUpdatesAbout->push_back( i ); + + if( nextPlayer->holdingID > 0 ) { + + nextPlayer->actionAttempt = 1; + nextPlayer->actionTarget.x = targetPos.x; + nextPlayer->actionTarget.y = targetPos.y; + + if( nextPlayer->actionTarget.x > nextPlayer->xd ) { + nextPlayer->facingOverride = 1; + } + else if( nextPlayer->actionTarget.x < nextPlayer->xd ) { + nextPlayer->facingOverride = -1; + } + + // holding something + ObjectRecord *heldObj = + getObject( nextPlayer->holdingID ); + + if( heldObj->deadlyDistance > 0 ) { + // it's deadly + + GridPos playerPos = getPlayerPos( nextPlayer ); + + double d = distance( targetPos, + playerPos ); + + if( heldObj->deadlyDistance >= d && + ! directLineBlocked( playerPos, + targetPos ) ) { + // target is close enough + // and no blocking objects along the way + + char someoneHit = false; + + + if( hitPlayer != NULL && + strstr( heldObj->description, + "otherFamilyOnly" ) ) { + // make sure victim is in + // different family + + if( hitPlayer->lineageEveID == + nextPlayer->lineageEveID ) { + + hitPlayer = NULL; + } + } + + + if( hitPlayer != NULL ) { + someoneHit = true; + // break the connection with + // them, eventually + // let them stagger a bit first + + hitPlayer->murderSourceID = + nextPlayer->holdingID; + + hitPlayer->murderPerpID = + nextPlayer->id; + + // brand this player as a murderer + nextPlayer->everKilledAnyone = true; + + if( hitPlayer->murderPerpEmail + != NULL ) { + delete [] + hitPlayer->murderPerpEmail; + } + + hitPlayer->murderPerpEmail = + stringDuplicate( + nextPlayer->email ); + + + setDeathReason( hitPlayer, + "killed", + nextPlayer->holdingID ); + + // if not already dying + if( ! hitPlayer->dying ) { + int staggerTime = + SettingsManager::getIntSetting( + "deathStaggerTime", 20 ); + + double currentTime = + Time::getCurrentTime(); + + hitPlayer->dying = true; + hitPlayer->dyingETA = + currentTime + staggerTime; + + playerIndicesToSendDyingAbout-> + push_back( + getLiveObjectIndex( + hitPlayer->id ) ); + + hitPlayer->errorCauseString = + "Player killed by other player"; + } + else { + // already dying, + // and getting attacked again + + // halve their remaining + // stagger time + double currentTime = + Time::getCurrentTime(); + + double staggerTimeLeft = + hitPlayer->dyingETA - + currentTime; + + if( staggerTimeLeft > 0 ) { + staggerTimeLeft /= 2; + hitPlayer->dyingETA = + currentTime + + staggerTimeLeft; + } + } + } + + + // a player either hit or not + // in either case, weapon was used + + // check for a transition for weapon + + // 0 is generic "on person" target + TransRecord *r = + getPTrans( nextPlayer->holdingID, + 0 ); + + TransRecord *rHit = NULL; + TransRecord *woundHit = NULL; + + if( someoneHit ) { + // last use on target specifies + // grave and weapon change on hit + // non-last use (r above) specifies + // what projectile ends up in grave + // or on ground + rHit = + getPTrans( nextPlayer->holdingID, + 0, false, true ); + + if( rHit != NULL && + rHit->newTarget > 0 ) { + hitPlayer->customGraveID = + rHit->newTarget; + } + + char wasSick = false; + + if( hitPlayer->holdingID > 0 && + strstr( + getObject( + hitPlayer->holdingID )-> + description, + "sick" ) != NULL ) { + wasSick = true; + } + + // last use on actor specifies + // what is left in victim's hand + woundHit = + getPTrans( nextPlayer->holdingID, + 0, true, false ); + + if( woundHit != NULL && + woundHit->newTarget > 0 ) { + + // don't drop their wound + if( hitPlayer->holdingID != 0 && + ! hitPlayer->holdingWound ) { + handleDrop( + targetPos.x, targetPos.y, + hitPlayer, + playerIndicesToSendUpdatesAbout ); + } + + // give them a new wound + // if they don't already have + // one, but never replace their + // original wound. That allows + // a healing exploit where you + // intentionally give someone + // an easier-to-treat wound + // to replace their hard-to-treat + // wound + + // however, do let wounds replace + // sickness + char woundChange = false; + + if( ! hitPlayer->holdingWound || + wasSick ) { + woundChange = true; + + hitPlayer->holdingID = + woundHit->newTarget; + holdingSomethingNew( + hitPlayer ); + setFreshEtaDecayForHeld( + hitPlayer ); + } + + + hitPlayer->holdingWound = true; + + if( woundChange ) { + + ForcedEffects e = + checkForForcedEffects( + hitPlayer->holdingID ); + + if( e.emotIndex != -1 ) { + hitPlayer->emotFrozen = + true; + newEmotPlayerIDs->push_back( + hitPlayer->id ); + newEmotIndices->push_back( + e.emotIndex ); + newEmotTTLs->push_back( + e.ttlSec ); + } + + if( e.foodModifierSet && + e.foodCapModifier != 1 ) { + + hitPlayer-> + foodCapModifier = + e.foodCapModifier; + hitPlayer->foodUpdate = + true; + } + + if( e.feverSet ) { + hitPlayer->fever = e.fever; + } + + checkSickStaggerTime( + hitPlayer ); + + playerIndicesToSendUpdatesAbout-> + push_back( + getLiveObjectIndex( + hitPlayer->id ) ); + } + } + } + + + int oldHolding = nextPlayer->holdingID; + timeSec_t oldEtaDecay = + nextPlayer->holdingEtaDecay; + + if( rHit != NULL ) { + // if hit trans exist + // leave bloody knife or + // whatever in hand + nextPlayer->holdingID = rHit->newActor; + holdingSomethingNew( nextPlayer, + oldHolding); + } + else if( woundHit != NULL ) { + // result of hit on held weapon + // could also be + // specified in wound trans + nextPlayer->holdingID = + woundHit->newActor; + holdingSomethingNew( nextPlayer, + oldHolding); + } + else if( r != NULL ) { + nextPlayer->holdingID = r->newActor; + holdingSomethingNew( nextPlayer, + oldHolding ); + } + + + if( r != NULL || rHit != NULL ) { + + nextPlayer->heldTransitionSourceID = 0; + + if( oldHolding != + nextPlayer->holdingID ) { + + setFreshEtaDecayForHeld( + nextPlayer ); + } + } + + + if( r != NULL ) { + + if( hitPlayer != NULL && + r->newTarget != 0 ) { + + hitPlayer->embeddedWeaponID = + r->newTarget; + + if( oldHolding == r->newTarget ) { + // what we are holding + // is now embedded in them + // keep old decay + hitPlayer-> + embeddedWeaponEtaDecay = + oldEtaDecay; + } + else { + + TransRecord *newDecayT = + getMetaTrans( + -1, + r->newTarget ); + + if( newDecayT != NULL ) { + hitPlayer-> + embeddedWeaponEtaDecay = + Time::timeSec() + + newDecayT-> + autoDecaySeconds; + } + else { + // no further decay + hitPlayer-> + embeddedWeaponEtaDecay + = 0; + } + } + } + else if( hitPlayer == NULL && + isMapSpotEmpty( targetPos.x, + targetPos.y ) ) { + // this is old code, and probably never gets executed + + // no player hit, and target ground + // spot is empty + setMapObject( targetPos.x, targetPos.y, + r->newTarget ); + + // if we're thowing a weapon + // target is same as what we + // were holding + if( oldHolding == r->newTarget ) { + // preserve old decay time + // of what we were holding + setEtaDecay( targetPos.x, targetPos.y, + oldEtaDecay ); + } + } + // else new target, post-kill-attempt + // is lost + } + } + } + } + } + + + + +void nameBaby( LiveObject *inNamer, LiveObject *inBaby, char *inName, + SimpleVector *playerIndicesToSendNamesAbout ) { + + LiveObject *nextPlayer = inNamer; + LiveObject *babyO = inBaby; + + char *name = inName; + + + const char *lastName = ""; + if( nextPlayer->name != NULL ) { + lastName = strstr( nextPlayer->name, + " " ); + + if( lastName != NULL ) { + // skip space + lastName = &( lastName[1] ); + } + + if( lastName == NULL ) { + lastName = ""; + + if( nextPlayer->familyName != + NULL ) { + lastName = + nextPlayer->familyName; + } + } + else if( nextPlayer->nameHasSuffix ) { + // only keep last name + // if it contains another + // space (the suffix is after + // the last name). Otherwise + // we are probably confused, + // and what we think + // is the last name IS the suffix. + + char *suffixPos = + strstr( (char*)lastName, " " ); + + if( suffixPos == NULL ) { + // last name is suffix, actually + // don't pass suffix on to baby + lastName = ""; + } + else { + // last name plus suffix + // okay to pass to baby + // because we strip off + // third part of name + // (suffix) below. + } + } + } + else if( nextPlayer->familyName != NULL ) { + lastName = nextPlayer->familyName; + } + else if( babyO->familyName != NULL ) { + lastName = babyO->familyName; + } + + + + const char *close = + findCloseFirstName( name ); + + babyO->name = autoSprintf( "%s %s", + close, + lastName ); + + if( babyO->familyName == NULL && + nextPlayer->familyName != NULL ) { + // mother didn't have a family + // name set when baby was born + // now she does + // or whatever player named + // this orphaned baby does + babyO->familyName = + stringDuplicate( + nextPlayer->familyName ); + } + + + int spaceCount = 0; + int lastSpaceIndex = -1; + + int nameLen = strlen( babyO->name ); + for( int s=0; sname[s] == ' ' ) { + lastSpaceIndex = s; + spaceCount++; + } + } + + if( spaceCount > 1 ) { + // remove suffix from end + babyO->name[ lastSpaceIndex ] = '\0'; + } + + babyO->name = getUniqueCursableName( + babyO->name, + &( babyO->nameHasSuffix ) ); + + logName( babyO->id, + babyO->email, + babyO->name, + babyO->lineageEveID ); + + playerIndicesToSendNamesAbout->push_back( + getLiveObjectIndex( babyO->id ) ); + } + + + + + int main() { @@ -7771,7 +8325,10 @@ int main() { eveName = SettingsManager::getStringSetting( "eveName", "EVE" ); - + + killEmotionIndex = + SettingsManager::getIntSetting( "killEmotionIndex", 2 ); + #ifdef WIN_32 printf( "\n\nPress CTRL-C to shut down server gracefully\n\n" ); @@ -10566,93 +11123,10 @@ int main() { if( babyO != NULL && babyO->name == NULL ) { char *name = isBabyNamingSay( m.saidText ); - - if( name != NULL && strcmp( name, "" ) != 0 ) { - const char *lastName = ""; - if( nextPlayer->name != NULL ) { - lastName = strstr( nextPlayer->name, - " " ); - - if( lastName != NULL ) { - // skip space - lastName = &( lastName[1] ); - } - - if( lastName == NULL ) { - lastName = ""; - - if( nextPlayer->familyName != - NULL ) { - lastName = - nextPlayer->familyName; - } - } - else if( nextPlayer->nameHasSuffix ) { - // only keep last name - // if it contains another - // space (the suffix is after - // the last name). Otherwise - // we are probably confused, - // and what we think - // is the last name IS the suffix. - - char *suffixPos = - strstr( (char*)lastName, " " ); - - if( suffixPos == NULL ) { - // last name is suffix, actually - // don't pass suffix on to baby - lastName = ""; - } - else { - // last name plus suffix - // okay to pass to baby - // because we strip off - // third part of name - // (suffix) below. - } - } - } - else if( nextPlayer->familyName != NULL ) { - lastName = nextPlayer->familyName; - } - - - const char *close = - findCloseFirstName( name ); - - babyO->name = autoSprintf( "%s %s", - close, - lastName ); - - int spaceCount = 0; - int lastSpaceIndex = -1; - - int nameLen = strlen( babyO->name ); - for( int s=0; sname[s] == ' ' ) { - lastSpaceIndex = s; - spaceCount++; - } - } - - if( spaceCount > 1 ) { - // remove suffix from end - babyO->name[ lastSpaceIndex ] = '\0'; - } - - babyO->name = getUniqueCursableName( - babyO->name, - &( babyO->nameHasSuffix ) ); - - logName( babyO->id, - babyO->email, - babyO->name, - babyO->lineageEveID ); - - playerIndicesToSendNamesAbout.push_back( - getLiveObjectIndex( babyO->id ) ); + if( name != NULL && strcmp( name, "" ) != 0 ) { + nameBaby( nextPlayer, babyO, name, + &playerIndicesToSendNamesAbout ); } } } @@ -10667,28 +11141,12 @@ int main() { LiveObject *closestOther = getClosestOtherPlayer( nextPlayer, - true, - babyAge ); + babyAge, true ); if( closestOther != NULL ) { - const char *close = - findCloseFirstName( name ); - - closestOther->name = - stringDuplicate( close ); - - closestOther->name = getUniqueCursableName( - closestOther->name, - &( closestOther->nameHasSuffix ) ); - - logName( closestOther->id, - closestOther->email, - closestOther->name, - closestOther->lineageEveID ); - - playerIndicesToSendNamesAbout.push_back( - getLiveObjectIndex( - closestOther->id ) ); + nameBaby( nextPlayer, closestOther, + name, + &playerIndicesToSendNamesAbout ); } } @@ -10734,371 +11192,27 @@ int main() { makePlayerSay( nextPlayer, m.saidText ); } else if( m.type == KILL ) { - // send update even if action fails (to let them - // know that action is over) playerIndicesToSendUpdatesAbout.push_back( i ); - - if( nextPlayer->holdingID > 0 && - ! (m.x == nextPlayer->xd && - m.y == nextPlayer->yd ) ) { + if( m.id > 0 && + nextPlayer->holdingID > 0 && + getObject( nextPlayer->holdingID )->deadlyDistance + > 0 ) { - nextPlayer->actionAttempt = 1; - nextPlayer->actionTarget.x = m.x; - nextPlayer->actionTarget.y = m.y; + // player transitioning into kill state - if( m.x > nextPlayer->xd ) { - nextPlayer->facingOverride = 1; - } - else if( m.x < nextPlayer->xd ) { - nextPlayer->facingOverride = -1; - } - - // holding something - ObjectRecord *heldObj = - getObject( nextPlayer->holdingID ); + LiveObject *targetPlayer = + getLiveObject( m.id ); - if( heldObj->deadlyDistance > 0 ) { - // it's deadly - - GridPos targetPos = { m.x, m.y }; - GridPos playerPos = { nextPlayer->xd, - nextPlayer->yd }; - - double d = distance( targetPos, - playerPos ); + if( targetPlayer != NULL ) { + nextPlayer->emotFrozen = true; + newEmotPlayerIDs.push_back( + nextPlayer->id ); + newEmotIndices.push_back( + killEmotionIndex ); + newEmotTTLs.push_back( 120 ); - if( heldObj->deadlyDistance >= d && - ! directLineBlocked( playerPos, - targetPos ) ) { - // target is close enough - // and no blocking objects along the way - - // is anyone there? - LiveObject *hitPlayer = - getHitPlayer( m.x, m.y, m.id, true ); - - char someoneHit = false; - - - if( hitPlayer != NULL && - strstr( heldObj->description, - "otherFamilyOnly" ) ) { - // make sure victim is in - // different family - - if( hitPlayer->lineageEveID == - nextPlayer->lineageEveID ) { - - hitPlayer = NULL; - } - } - - - if( hitPlayer != NULL ) { - someoneHit = true; - // break the connection with - // them, eventually - // let them stagger a bit first - - hitPlayer->murderSourceID = - nextPlayer->holdingID; - - hitPlayer->murderPerpID = - nextPlayer->id; - - // brand this player as a murderer - nextPlayer->everKilledAnyone = true; - - if( hitPlayer->murderPerpEmail - != NULL ) { - delete [] - hitPlayer->murderPerpEmail; - } - - hitPlayer->murderPerpEmail = - stringDuplicate( - nextPlayer->email ); - - - setDeathReason( hitPlayer, - "killed", - nextPlayer->holdingID ); - - // if not already dying - if( ! hitPlayer->dying ) { - int staggerTime = - SettingsManager::getIntSetting( - "deathStaggerTime", 20 ); - - double currentTime = - Time::getCurrentTime(); - - hitPlayer->dying = true; - hitPlayer->dyingETA = - currentTime + staggerTime; - - playerIndicesToSendDyingAbout. - push_back( - getLiveObjectIndex( - hitPlayer->id ) ); - - hitPlayer->errorCauseString = - "Player killed by other player"; - } - else { - // already dying, - // and getting attacked again - - // halve their remaining - // stagger time - double currentTime = - Time::getCurrentTime(); - - double staggerTimeLeft = - hitPlayer->dyingETA - - currentTime; - - if( staggerTimeLeft > 0 ) { - staggerTimeLeft /= 2; - hitPlayer->dyingETA = - currentTime + - staggerTimeLeft; - } - } - } - - - // a player either hit or not - // in either case, weapon was used - - // check for a transition for weapon - - // 0 is generic "on person" target - TransRecord *r = - getPTrans( nextPlayer->holdingID, - 0 ); - - TransRecord *rHit = NULL; - TransRecord *woundHit = NULL; - - if( someoneHit ) { - // last use on target specifies - // grave and weapon change on hit - // non-last use (r above) specifies - // what projectile ends up in grave - // or on ground - rHit = - getPTrans( nextPlayer->holdingID, - 0, false, true ); - - if( rHit != NULL && - rHit->newTarget > 0 ) { - hitPlayer->customGraveID = - rHit->newTarget; - } - - char wasSick = false; - - if( hitPlayer->holdingID > 0 && - strstr( - getObject( - hitPlayer->holdingID )-> - description, - "sick" ) != NULL ) { - wasSick = true; - } - - // last use on actor specifies - // what is left in victim's hand - woundHit = - getPTrans( nextPlayer->holdingID, - 0, true, false ); - - if( woundHit != NULL && - woundHit->newTarget > 0 ) { - - // don't drop their wound - if( hitPlayer->holdingID != 0 && - ! hitPlayer->holdingWound ) { - handleDrop( - m.x, m.y, - hitPlayer, - &playerIndicesToSendUpdatesAbout ); - } - - // give them a new wound - // if they don't already have - // one, but never replace their - // original wound. That allows - // a healing exploit where you - // intentionally give someone - // an easier-to-treat wound - // to replace their hard-to-treat - // wound - - // however, do let wounds replace - // sickness - char woundChange = false; - - if( ! hitPlayer->holdingWound || - wasSick ) { - woundChange = true; - - hitPlayer->holdingID = - woundHit->newTarget; - holdingSomethingNew( - hitPlayer ); - setFreshEtaDecayForHeld( - hitPlayer ); - } - - - hitPlayer->holdingWound = true; - - if( woundChange ) { - - ForcedEffects e = - checkForForcedEffects( - hitPlayer->holdingID ); - - if( e.emotIndex != -1 ) { - hitPlayer->emotFrozen = - true; - newEmotPlayerIDs.push_back( - hitPlayer->id ); - newEmotIndices.push_back( - e.emotIndex ); - newEmotTTLs.push_back( - e.ttlSec ); - } - - if( e.foodModifierSet && - e.foodCapModifier != 1 ) { - - hitPlayer-> - foodCapModifier = - e.foodCapModifier; - hitPlayer->foodUpdate = - true; - } - - if( e.feverSet ) { - hitPlayer->fever = e.fever; - } - - checkSickStaggerTime( - hitPlayer ); - - playerIndicesToSendUpdatesAbout. - push_back( - getLiveObjectIndex( - hitPlayer->id ) ); - } - } - } - - - int oldHolding = nextPlayer->holdingID; - timeSec_t oldEtaDecay = - nextPlayer->holdingEtaDecay; - - if( rHit != NULL ) { - // if hit trans exist - // leave bloody knife or - // whatever in hand - nextPlayer->holdingID = rHit->newActor; - holdingSomethingNew( nextPlayer, - oldHolding); - } - else if( woundHit != NULL ) { - // result of hit on held weapon - // could also be - // specified in wound trans - nextPlayer->holdingID = - woundHit->newActor; - holdingSomethingNew( nextPlayer, - oldHolding); - } - else if( r != NULL ) { - nextPlayer->holdingID = r->newActor; - holdingSomethingNew( nextPlayer, - oldHolding ); - } - - - if( r != NULL || rHit != NULL ) { - - nextPlayer->heldTransitionSourceID = 0; - - if( oldHolding != - nextPlayer->holdingID ) { - - setFreshEtaDecayForHeld( - nextPlayer ); - } - } - - - if( r != NULL ) { - - if( hitPlayer != NULL && - r->newTarget != 0 ) { - - hitPlayer->embeddedWeaponID = - r->newTarget; - - if( oldHolding == r->newTarget ) { - // what we are holding - // is now embedded in them - // keep old decay - hitPlayer-> - embeddedWeaponEtaDecay = - oldEtaDecay; - } - else { - - TransRecord *newDecayT = - getMetaTrans( - -1, - r->newTarget ); - - if( newDecayT != NULL ) { - hitPlayer-> - embeddedWeaponEtaDecay = - Time::timeSec() + - newDecayT-> - autoDecaySeconds; - } - else { - // no further decay - hitPlayer-> - embeddedWeaponEtaDecay - = 0; - } - } - } - else if( hitPlayer == NULL && - isMapSpotEmpty( m.x, - m.y ) ) { - // no player hit, and target ground - // spot is empty - setMapObject( m.x, m.y, - r->newTarget ); - - // if we're thowing a weapon - // target is same as what we - // were holding - if( oldHolding == r->newTarget ) { - // preserve old decay time - // of what we were holding - setEtaDecay( m.x, m.y, - oldEtaDecay ); - } - } - // else new target, post-kill-attempt - // is lost - } - } + addKillState( nextPlayer->id, + targetPlayer->id ); } } } @@ -11765,9 +11879,12 @@ int main() { computeAge( nextPlayer ) ) ) { canPlace = true; + + ObjectRecord *newTargetObj = + getObject( r->newTarget ); + - if( getObject( r->newTarget )-> - blocksWalking && + if( newTargetObj->blocksWalking && ! isMapSpotEmpty( m.x, m.y ) ) { // can't do on-bare ground @@ -11777,6 +11894,17 @@ int main() { // object canPlace = false; } + else if( + strstr( newTargetObj->description, + "groundOnly" ) != NULL + && + getMapFloor( m.x, m.y ) != 0 ) { + // floor present + + // new target not allowed + // to exist on floor + canPlace = false; + } } if( canPlace ) { @@ -13148,6 +13276,71 @@ int main() { } } + + // process pending KILL actions + for( int i=0; ikillerID ); + LiveObject *target = getLiveObject( s->targetID ); + + if( killer == NULL || target == NULL || + killer->error || target->error || + killer->holdingID != s->killerWeaponID ) { + // either player dead, or held-weapon change + + // kill request done + if( killer != NULL ) { + // clear their emot + killer->emotFrozen = false; + + newEmotPlayerIDs.push_back( killer->id ); + + newEmotIndices.push_back( -1 ); + newEmotTTLs.push_back( 0 ); + } + + activeKillStates.deleteElement( i ); + i--; + continue; + } + + // kill request still active! + + // see if it is realized (close enough)? + GridPos playerPos = getPlayerPos( killer ); + GridPos targetPos = getPlayerPos( target ); + + double dist = distance( playerPos, targetPos ); + + if( getObject( killer->holdingID )->deadlyDistance >= dist && + ! directLineBlocked( playerPos, targetPos ) ) { + // close enough to kill + + executeKillAction( getLiveObjectIndex( s->killerID ), + getLiveObjectIndex( s->targetID ), + &playerIndicesToSendUpdatesAbout, + &playerIndicesToSendDyingAbout, + &newEmotPlayerIDs, + &newEmotIndices, + &newEmotTTLs ); + } + else { + // still not close enough + // see if we need to renew emote + double curTime = Time::getCurrentTime(); + + if( curTime - s->emotStartTime > 30 ) { + s->emotStartTime = curTime; + + newEmotPlayerIDs.push_back( killer->id ); + + newEmotIndices.push_back( killEmotionIndex ); + newEmotTTLs.push_back( 0 ); + } + } + } + // now that messages have been processed for all @@ -13370,6 +13563,13 @@ int main() { deathID = nextPlayer->customGraveID; } + char deathMarkerHasSlots = false; + + if( deathID > 0 ) { + deathMarkerHasSlots = + ( getObject( deathID )->numSlots > 0 ); + } + int oldObject = getMapObject( dropPos.x, dropPos.y ); SimpleVector oldContained; @@ -13390,7 +13590,8 @@ int main() { if( ! isGrave( oldObject ) ) { ObjectRecord *r = getObject( oldObject ); - if( r->numSlots == 0 && ! r->permanent + if( deathMarkerHasSlots && + r->numSlots == 0 && ! r->permanent && ! r->rideable ) { // found a containble object @@ -14553,6 +14754,12 @@ int main() { if( envHeatTarget < targetHeat ) { // we're in a cold environment + + if( nextPlayer->isIndoors ) { + float targetDiff = targetHeat - envHeatTarget; + float indoorAdjustedDiff = targetDiff / 2; + envHeatTarget = targetHeat - indoorAdjustedDiff; + } // clothing actually reduces how cold it is // based on its R-value @@ -14567,6 +14774,21 @@ int main() { float clothingAdjustedDiff = targetDiff / ( 1 + clothingR ); + // how much did clothing improve our situation? + float improvement = targetDiff - clothingAdjustedDiff; + + if( nextPlayer->isIndoors ) { + // if indoors, double the improvement of clothing + // thus, if it took us half-way to perfect, being + // indoors will take us all the way to perfect + // think about this as a reduction in the wind chill + // factor + + improvement *= 2; + } + clothingAdjustedDiff = targetDiff - improvement; + + envHeatTarget = targetHeat - clothingAdjustedDiff; } @@ -16291,18 +16513,33 @@ int main() { speakerAge = listenerAge; } - char *translatedPhrase = - mapLanguagePhrase( - newSpeechPhrases.getElementDirect( u ), - speakerEveID, - listenerEveID, - speakerID, - listenerID, - speakerAge, - listenerAge, - speakerParentID, - listenerParentID ); - + + char *translatedPhrase; + + if( nextPlayer->vogMode || + ( speakerObj != NULL && + speakerObj->vogMode ) ) { + + translatedPhrase = + stringDuplicate( + newSpeechPhrases. + getElementDirect( u ) ); + } + else { + translatedPhrase = + mapLanguagePhrase( + newSpeechPhrases. + getElementDirect( u ), + speakerEveID, + listenerEveID, + speakerID, + listenerID, + speakerAge, + listenerAge, + speakerParentID, + listenerParentID ); + } + int curseFlag = newSpeechCurseFlags.getElementDirect( u ); diff --git a/server/settings/killEmotionIndex.ini b/server/settings/killEmotionIndex.ini new file mode 100644 index 000000000..d8263ee98 --- /dev/null +++ b/server/settings/killEmotionIndex.ini @@ -0,0 +1 @@ +2 \ No newline at end of file