diff --git a/LICENSE b/LICENSE deleted file mode 100644 index cf1ab25da..000000000 --- a/LICENSE +++ /dev/null @@ -1,24 +0,0 @@ -This is free and unencumbered software released into the public domain. - -Anyone is free to copy, modify, publish, use, compile, sell, or -distribute this software, either in source code form or as a compiled -binary, for any purpose, commercial or non-commercial, and by any -means. - -In jurisdictions that recognize copyright laws, the author or authors -of this software dedicate any and all copyright interest in the -software to the public domain. We make this dedication for the benefit -of the public at large and to the detriment of our heirs and -successors. We intend this dedication to be an overt act of -relinquishment in perpetuity of all present and future rights to this -software under copyright law. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR -OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, -ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -OTHER DEALINGS IN THE SOFTWARE. - -For more information, please refer to diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 000000000..73d9d02c9 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,54 @@ +# Sustainable Use License + +Version 1.0 + +## Acceptance + +By using the software, you agree to all of the terms and conditions below. + +## Copyright License + +The licensor grants you a non-exclusive, royalty-free, worldwide, non-sublicensable, non-transferable license to use, copy, distribute, make available, and prepare derivative works of the software, in each case subject to the limitations below. + +## Limitations + +You may use or modify the software only for your own internal business purposes or for non-commercial or personal use. +You may distribute the software or provide it to others only if you do so free of charge for non-commercial purposes. +You may not alter, remove, or obscure any licensing, copyright, or other notices of the licensor in the software. Any use of the licensor’s trademarks is subject to applicable law. + +## Patents + +The licensor grants you a license, under any patent claims the licensor can license, or becomes able to license, to make, have made, use, sell, offer for sale, import and have imported the software, in each case subject to the limitations and conditions in this license. This license does not cover any patent claims that you cause to be infringed by modifications or additions to the software. If you or your company make any written claim that the software infringes or contributes to infringement of any patent, your patent license for the software granted under these terms ends immediately. If your company makes such a claim, your patent license ends immediately for work on behalf of your company. + +## Notices + +You must ensure that anyone who gets a copy of any part of the software from you also gets a copy of these terms. +If you modify the software, you must include in any modified copies of the software a prominent notice stating that you have modified the software. + +## No Other Rights + +These terms do not imply any licenses other than those expressly granted in these terms. + +## Termination + +If you use the software in violation of these terms, such use is not licensed, and your license will automatically terminate. If the licensor provides you with a notice of your violation, and you cease all violation of this license no later than 30 days after you receive that notice, your license will be reinstated retroactively. However, if you violate these terms after such reinstatement, any additional violation of these terms will cause your license to terminate automatically and permanently. + +## No Liability + +As far as the law allows, the software comes as is, without any warranty or condition, and the licensor will not be liable to you for any damages arising out of these terms or the use or nature of the software, under any kind of legal claim. + +## Definitions + +The “licensor” is the entity offering these terms. + +The “software” is the software the licensor makes available under these terms, including any portion of it. + +“You” refers to the individual or entity agreeing to these terms. + +“Your company” is any legal entity, sole proprietorship, or other kind of organization that you work for, plus all organizations that have control over, are under the control of, or are under common control with that organization. Control means ownership of substantially all the assets of an entity, or the power to direct its management and policies by vote, contract, or otherwise. Control can be direct or indirect. + +“Your license” is the license granted to you for the software under these terms. + +“Use” means anything you do with the software requiring your license. + +“Trademark” means trademarks, service marks, and similar rights. diff --git a/README.md b/README.md index a46c40b8f..7ae6851d6 100644 --- a/README.md +++ b/README.md @@ -76,7 +76,7 @@ Want to compile the program by yourself? Great! Simply follow the [build instruc # Legal -DevilutionX is released to the Public Domain. The documentation and functionality provided by DevilutionX may only be utilized with assets provided by the ownership of Diablo. +DevilutionX is made publicly available and released under the Sustainable Use License (see [LICENSE](LICENSE.md)) The source code in this repository is for non-commercial use only. If you use the source code you may not charge others for access to it or any derivative work thereof. diff --git a/Source/items.cpp b/Source/items.cpp index baf746e87..6a660e5ff 100644 --- a/Source/items.cpp +++ b/Source/items.cpp @@ -2350,7 +2350,6 @@ void InitItems() ShowUniqueItemInfoBox = false; - // BUGFIX: item get records not reset when resetting items (fixed). initItemGetRecords(); } @@ -4367,6 +4366,7 @@ bool GetItemRecord(int nSeed, uint16_t wCI, int nIndex) for (int i = 0; i < gnNumGetRecords; i++) { if (ticks - itemrecord[i].dwTimestamp > 6000) { + // BUGFIX: loot actions for multiple quest items with same seed (e.g. blood stone) performed within less then 6 seconds will be ignored. NextItemRecord(i); i--; } else if (nSeed == itemrecord[i].nSeed && wCI == itemrecord[i].wCI && nIndex == itemrecord[i].nIndex) { diff --git a/Source/levels/drlg_l1.cpp b/Source/levels/drlg_l1.cpp index bb612a2b0..7c6b2fc0b 100644 --- a/Source/levels/drlg_l1.cpp +++ b/Source/levels/drlg_l1.cpp @@ -1246,6 +1246,7 @@ void PlaceMiniSetRandom(const Miniset &miniset, int rndper) for (int sx = 0; sx < DMAXX - sw; sx++) { if (!miniset.matches({ sx, sy }, false)) continue; + // BUGFIX: This code is copied from Cave and should not be applied for crypt if (!CanReplaceTile(miniset.replace[0][0], { sx, sy })) continue; if (GenerateRnd(100) >= rndper) diff --git a/Source/levels/drlg_l3.cpp b/Source/levels/drlg_l3.cpp index d8c8d034d..58d5d1eda 100644 --- a/Source/levels/drlg_l3.cpp +++ b/Source/levels/drlg_l3.cpp @@ -1395,6 +1395,7 @@ bool PlaceMiniSetRandom(const Miniset &miniset, int rndper) for (int sx = 0; sx < DMAXX - sw; sx++) { if (!miniset.matches({ sx, sy })) continue; + // BUGFIX: This should not be applied to Nest levels if (!CanReplaceTile(miniset.replace[0][0], { sx, sy })) continue; if (GenerateRnd(100) >= rndper) diff --git a/Source/levels/gendung.h b/Source/levels/gendung.h index 4e50dd958..52fb818fb 100644 --- a/Source/levels/gendung.h +++ b/Source/levels/gendung.h @@ -281,7 +281,7 @@ struct Miniset { /** * @param position Coordinates of the dungeon tile to check - * @param respectProtected Match bug from Crypt levels + * @param respectProtected Match bug from Crypt levels if false */ bool matches(Point position, bool respectProtected = true) const { diff --git a/Source/levels/themes.cpp b/Source/levels/themes.cpp index c546154d3..5893485d2 100644 --- a/Source/levels/themes.cpp +++ b/Source/levels/themes.cpp @@ -661,6 +661,10 @@ void Theme_Treasure(int t) CreateRndItem({ xp, yp }, false, false, true); ItemNoFlippy(); } + // BUGFIX: the following code is likely not working as intended. + // `rv >= treasureType - 2` is not connected to either + // of the item creation branches above, thus the last (unrelated) + // item spawned/dropped on ground would be halved in value. if (rv >= treasureType - 2 && leveltype != DTYPE_CATHEDRAL) { Item &item = Items[ActiveItems[ActiveItemCount - 1]]; if (item.IDidx == IDI_GOLD) { diff --git a/Source/loadsave.cpp b/Source/loadsave.cpp index 27505a01e..69da533b1 100644 --- a/Source/loadsave.cpp +++ b/Source/loadsave.cpp @@ -2169,8 +2169,8 @@ void LoadGame(bool firstflag) dObject[i][j] = file.NextLE(); } for (int j = 0; j < MAXDUNY; j++) { - for (int i = 0; i < MAXDUNX; i++) // NOLINT(modernize-loop-convert) - dLight[i][j] = file.NextLE(); + for (int i = 0; i < MAXDUNX; i++) // NOLINT(modernize-loop-convert) + dLight[i][j] = file.NextLE(); // BUGFIX: dLight got loaded already } for (int j = 0; j < MAXDUNY; j++) { for (int i = 0; i < MAXDUNX; i++) // NOLINT(modernize-loop-convert) @@ -2426,8 +2426,8 @@ void SaveGameData(MpqWriter &saveWriter) file.WriteLE(dObject[i][j]); } for (int j = 0; j < MAXDUNY; j++) { - for (int i = 0; i < MAXDUNX; i++) // NOLINT(modernize-loop-convert) - file.WriteLE(dLight[i][j]); + for (int i = 0; i < MAXDUNX; i++) // NOLINT(modernize-loop-convert) + file.WriteLE(dLight[i][j]); // BUGFIX: dLight got saved already } for (int j = 0; j < MAXDUNY; j++) { for (int i = 0; i < MAXDUNX; i++) // NOLINT(modernize-loop-convert) diff --git a/Source/misdat.h b/Source/misdat.h index 434f05857..1f93009be 100644 --- a/Source/misdat.h +++ b/Source/misdat.h @@ -90,7 +90,7 @@ typedef enum missile_graphic_id : uint8_t { MFILE_BONEDEMON, MFILE_EXORA1, MFILE_EXBL3, - MFILE_NONE, // BUGFIX: should be `MFILE_NONE = MFILE_SCBSEXPD+1`, i.e. MFILE_NULL, since there would otherwise be an out-of-bounds in SetMissAnim when accessing MissileSpriteData for any of the missiles that have MFILE_NONE as mFileNum in MissileData. (fixed) + MFILE_NONE, } missile_graphic_id; /** diff --git a/Source/missiles.cpp b/Source/missiles.cpp index 073369bb7..7c2aa79c8 100644 --- a/Source/missiles.cpp +++ b/Source/missiles.cpp @@ -747,8 +747,8 @@ void GetDamageAmt(spell_id i, int *mind, int *maxd) *mind = AddClassHealingBonus(myPlayer._pLevel + sl + 1, myPlayer._pClass) - 1; *maxd = AddClassHealingBonus((4 * myPlayer._pLevel) + (6 * sl) + 10, myPlayer._pClass) - 1; break; - case SPL_LIGHTNING: case SPL_RUNELIGHT: + case SPL_LIGHTNING: *mind = 2; *maxd = 2 + myPlayer._pLevel; break; @@ -2681,10 +2681,12 @@ void MI_LArrow(Missile &missile) missile._midist++; if (!missile.IsTrap()) { if (missile._micaster == TARGET_MONSTERS) { + // BUGFIX: damage of missile should be encoded in missile struct; player can be dead/have left the game before missile arrives. const Player &player = Players[p]; mind = player._pIMinDam; maxd = player._pIMaxDam; } else { + // BUGFIX: damage of missile should be encoded in missile struct; monster can be dead before missile arrives. Monster &monster = Monsters[p]; mind = monster.minDamage; maxd = monster.maxDamage; @@ -2711,6 +2713,7 @@ void MI_LArrow(Missile &missile) switch (missile._mitype) { case MIS_LARROW: if (!missile.IsTrap()) { + // BUGFIX: damage of missile should be encoded in missile struct; player can be dead/have left the game before missile arrives. const Player &player = Players[p]; eMind = player._pILMinDam; eMaxd = player._pILMaxDam; @@ -2723,6 +2726,7 @@ void MI_LArrow(Missile &missile) break; case MIS_FARROW: if (!missile.IsTrap()) { + // BUGFIX: damage of missile should be encoded in missile struct; player can be dead/have left the game before missile arrives. const Player &player = Players[p]; eMind = player._pIFMinDam; eMaxd = player._pIFMaxDam; @@ -2765,11 +2769,13 @@ void MI_Arrow(Missile &missile) int maxd; switch (missile.sourceType()) { case MissileSource::Player: { + // BUGFIX: damage of missile should be encoded in missile struct; player can be dead/have left the game before missile arrives. const Player &player = *missile.sourcePlayer(); mind = player._pIMinDam; maxd = player._pIMaxDam; } break; case MissileSource::Monster: { + // BUGFIX: damage of missile should be encoded in missile struct; monster can be dead before missile arrives. const Monster &monster = *missile.sourceMonster(); mind = monster.minDamage; maxd = monster.maxDamage; @@ -2796,9 +2802,11 @@ void MI_Firebolt(Missile &missile) const Player &player = *missile.sourcePlayer(); switch (missile._mitype) { case MIS_FIREBOLT: + // BUGFIX: damage of missile should be encoded in missile struct; player can be dead/have left the game before missile arrives. d = GenerateRnd(10) + (player._pMagic / 8) + missile._mispllvl + 1; break; case MIS_FLARE: + // BUGFIX: damage of missile should be encoded in missile struct; player can be dead/have left the game before missile arrives. d = 3 * missile._mispllvl - (player._pMagic / 8) + (player._pMagic / 2); break; case MIS_BONESPIRIT: @@ -2810,6 +2818,7 @@ void MI_Firebolt(Missile &missile) } break; case MissileSource::Monster: { const Monster &monster = *missile.sourceMonster(); + // BUGFIX: damage of missile should be encoded in missile struct; monster can be dead before missile arrives. d = monster.minDamage + GenerateRnd(monster.maxDamage - monster.minDamage + 1); } break; case MissileSource::Trap: @@ -3237,8 +3246,10 @@ void MI_Lightctrl(Missile &missile) int dam; if (missile.IsTrap()) { + // BUGFIX: damage of missile should be encoded in missile struct; monster can be dead before missile arrives. dam = GenerateRnd(currlevel) + 2 * currlevel; } else if (missile._micaster == TARGET_MONSTERS) { + // BUGFIX: damage of missile should be encoded in missile struct; player can be dead/have left the game before missile arrives. dam = (GenerateRnd(2) + GenerateRnd(Players[missile._misource]._pLevel) + 2) << 6; } else { auto &monster = Monsters[missile._misource]; @@ -3474,10 +3485,12 @@ void MI_Weapexp(Missile &missile) int mind; int maxd; if (missile.var2 == 1) { + // BUGFIX: damage of missile should be encoded in missile struct; player can be dead/have left the game before missile arrives. mind = player._pIFMinDam; maxd = player._pIFMaxDam; MissilesData[missile._mitype].mResist = MISR_FIRE; } else { + // BUGFIX: damage of missile should be encoded in missile struct; player can be dead/have left the game before missile arrives. mind = player._pILMinDam; maxd = player._pILMaxDam; MissilesData[missile._mitype].mResist = MISR_LIGHTNING; diff --git a/Source/monster.cpp b/Source/monster.cpp index 6fdb5b9ca..6edfa73d4 100644 --- a/Source/monster.cpp +++ b/Source/monster.cpp @@ -2259,7 +2259,6 @@ void FallenAi(Monster &monster) for (int x = -rad; x <= rad; x++) { int xpos = monster.position.tile.x + x; int ypos = monster.position.tile.y + y; - // BUGFIX: incorrect check of offset against limits of the dungeon (fixed) if (InDungeonBounds({ xpos, ypos })) { int m = dMonster[xpos][ypos]; if (m <= 0) @@ -3967,6 +3966,7 @@ void ProcessMonsters() if ((monster.flags & MFLAG_TARGETS_MONSTER) != 0) { assert(monster.enemy >= 0 && monster.enemy < MaxMonsters); + // BUGFIX: enemy target may be dead at time of access, thus reading garbage data from `Monsters[monster.enemy].position.future`. monster.position.last = Monsters[monster.enemy].position.future; monster.enemyPosition = monster.position.last; } else { diff --git a/Source/msg.cpp b/Source/msg.cpp index 028a556f1..a84618a46 100644 --- a/Source/msg.cpp +++ b/Source/msg.cpp @@ -1570,7 +1570,7 @@ size_t OnKillGolem(const TCmd *pCmd, size_t pnum) Monster &monster = Monsters[pnum]; if (player.isOnActiveLevel()) M_SyncStartKill(monster, position, player); - delta_kill_monster(monster, position, player); + delta_kill_monster(monster, position, player); // BUGFIX: should be p->wParam1, plrlevel will be incorrect if golem is killed because player changed levels } } else { SendPacket(pnum, &message, sizeof(message)); @@ -2293,6 +2293,7 @@ void delta_kill_monster(const Monster &monster, Point position, const Player &pl sgbDeltaChanged = true; DMonsterStr *pD = &GetDeltaLevel(player).monster[monster.getId()]; pD->position = position; + // BUGFIX: should only sync monster direction if bLevel is same as currlevel. pD->_mdir = monster.direction; pD->hitPoints = 0; }