*cracks knuckles* I haven't done one of these kind of posts in a while.
alright then
First: if you doubt their work quantity, check their activity on github, not in pulls but in sheer amount of code, and at least double it for anything not XML
the amount of shit done is huge and everytime a mechanic is added it often becomes part of the shit to take into account when making anything new
Lines of code is a very poor metric for productivity. One can very easily commit thousands of lines with minor refactors alone, and those can be handled automatically by an IDE, or simply moving things around. I remember seeing the latter quite a bit when I was routinely sifting through LT's code, and it seems as though that's still happening once in a while.
Organization and refactoring are both fine, but it's also very good at inadvertently padding out commits, especially given that SCMs will flag lines as "changed" if you so much as change the amount or type of whitespace. There really isn't a good way to account for that.
Even if we're referring to new code, you need to actually look at the code and not just how much of it there is. A 50-line "Hello World" program in a given language is almost always objectively worse than a 1-liner. Plus, there are still plenty of things that are implemented in Java that really shouldn't be. Here's an example from a recent commit (
You must be registered to see the links
):
Java:
public static AbstractStatusEffect SET_SUBMISSIVE_STEED = new AbstractStatusEffect(70,
"Domesticated Beast",
"clothingSets/submissive_steed",
PresetColour.CLOTHING_STEEL,
true,
Util.newHashMapOfValues(
new Value<>(Attribute.MAJOR_PHYSIQUE, 5f),
new Value<>(Attribute.RESISTANCE_LUST, -5f)),
null) {
@Override
public StatusEffectCategory getCategory() {
return StatusEffectCategory.INVENTORY;
}
@Override
public String getDescription(GameCharacter target) {
if(target!=null) {
return UtilText.parse(target, "By wearing a full set of tack, [npc.name] [npc.verb(find)] [npc.herself] feeling like a loyal, submissive beast, and [npc.verb(want)] to work hard for [npc.her] owner!");
} else {
return "";
}
}
@Override
public boolean isConditionsMet(GameCharacter target) {
return SetBonus.getSetBonusFromId("innoxia_submissive_steed").isCharacterWearingCompleteSet(target);
}
};
26 lines of code to add a status effect, which should probably be in XML. Here's a huge wall of "code" from
You must be registered to see the links
(not trying to single out DSG here; there's really doesn't seem to be any other way to define quests at the moment):
Java:
//Eisek Quests
EISEK_STALL_QUEST_STAGE_ONE(QuestType.SIDE,
1,
10) {
@Override
public String getName() {
return "Gathering Materials";
}
@Override
public String getDescription() {
return "You've learned what Eisek needs to fix up his stall, as well as what he would like on a new sign. Now you just need to gather some materials from the merchants around town for a nice surprise. Maybe there's one that deals in fabric?";
}
@Override
public String getCompletedDescription() {
return "You've ordered a new sign and some bolts of cloth from Monica.";
}
},
EISEK_STALL_QUEST_STAGE_TWO(QuestType.SIDE,
1,
10) {
@Override
public String getName() {
return "Need an Awning Here";
}
@Override
public String getDescription() {
return "While you're waiting for Monica to finish your order, you need to find some wooden poles for the awning. Perhaps the local smithy could modify the haft of a polearm could be modified somehow?";
}
@Override
public String getCompletedDescription() {
return "You've placed an order with Imsu and Hale for some modified axe hafts.";
}
},
EISEK_STALL_QUEST_STAGE_THREE(QuestType.SIDE,
1,
10) {
@Override
public String getName() {
return "It's All Coming Together";
}
@Override
public String getDescription() {
return "You should check back in with Hale in a day and Monica in three days to see if your order is ready yet.";
}
@Override
public String getCompletedDescription() {
return "You've gathered all the materials.";
}
},
EISEK_STALL_QUEST_STAGE_FOUR(QuestType.SIDE,
1,
10) {
@Override
public String getName() {
return "Putting it all together";
}
@Override
public String getDescription() {
return "You have what you need to improve Eisek's stall. Tell him about it the next time you see him.";
}
@Override
public String getCompletedDescription() {
return "As far as you could tell, Eisek was overjoyed at what you've done for him and his stall looks better than ever.";
}
},
EISEK_MOB_QUEST_STAGE_ONE(QuestType.SIDE,
10,
25) {
@Override
public String getName() {
return "One against Many";
}
@Override
public String getDescription() {
return "Eisek explained why there was a mob hounding him, but he doesn't know much about them. If you want to make sure they don't come back, you'll have to find them and confront them."
+ "<br/>As they seemed to be comprised of locals, maybe a search around town will do the trick.";
}
@Override
public String getCompletedDescription() {
return "Through a bit of luck and the mob putting up a big colourful poster, you've found and entered their meeting place.";
}
},
EISEK_MOB_QUEST_STAGE_TWO(QuestType.SIDE,
10,
100) {
@Override
public String getName() {
return "Leave the Dragon Alone!";
}
@Override
public String getDescription() {
return "You've found where the mob has been meeting. Time to handle them!";
}
@Override
public String getCompletedDescription() {
if(Main.game.getDialogueFlags().hasFlag(DialogueFlagValue.getDialogueFlagValueFromId("dsg_elis_eisek_mob_quest_intimidate"))) {
return "You decided to try and convince the mob to leave Eisek alone with your intimidating physique.";
} else if (Main.game.getDialogueFlags().hasFlag(DialogueFlagValue.getDialogueFlagValueFromId("dsg_elis_eisek_mob_quest_intimidate_arcane"))) {
return "You decided to try and convince the mob to leave Eisek alone with your arcane prowess.";
} else if (Main.game.getDialogueFlags().hasFlag(DialogueFlagValue.getDialogueFlagValueFromId("dsg_elis_eisek_mob_quest_persuade"))) {
if(!Main.game.isSillyModeEnabled()) {
return "You convinced the mob to leave Eisek alone with a heartfelt speech.";
} else {
return "You destroyed the mob's arguments with FACTS and LOGIC.";
}
} else if (Main.game.getDialogueFlags().hasFlag(DialogueFlagValue.getDialogueFlagValueFromId("dsg_elis_eisek_mob_quest_seduce"))) {
return "You convinced the mob to leave Eisek alone by using your mastery of lust magic to fuel an orgy.";
} else {
return "You weren't able to convince the mob to leave Eisek alone.";
}
}
},
EISEK_MOB_QUEST_STAGE_TWO_FAILED(QuestType.SIDE,
10,
0) {
@Override
public String getName() {
return "Tossed Out";
}
@Override
public String getDescription() {
return "You weren't able to convince the mob to leave Eisek alone. You should return to him with the bad news now that you weren't able to handle the mob.";
}
@Override
public String getCompletedDescription() {
return "You weren't able to convince the mob to leave Eisek alone.";
}
},
EISEK_MOB_QUEST_STAGE_THREE_FAILED(QuestType.SIDE,
10,
0) {
@Override
public String getName() {
return "Bad News";
}
@Override
public String getDescription() {
return "";
}
@Override
public String getCompletedDescription() {
return "Although he tried to hide it, Eisek seemed upset that the mob is still somewhere out there plotting against him.";
}
},
EISEK_MOB_QUEST_STAGE_THREE(QuestType.SIDE,
10,
250) {
@Override
public String getName() {
return "Good News";
}
@Override
public String getDescription() {
return "You should return to Eisek with the good news now that you've handled the mob.";
}
@Override
public String getCompletedDescription() {
return "Although he tried to hide it, Eisek seemed pretty happy that the mob will now leave him alone. You even got some rare Dragonfruit.";
}
},
EISEK_SILLYMODE_QUEST_STAGE_ONE(QuestType.SIDE,
1,
10) {
@Override
public String getName() {
return "Strange Crowd";
}
@Override
public String getDescription() {
return "You encountered a different sort of mob that was weirdly obsessed with Eisek. It ultimately came to nothing but you've decided to see what these strange people were all about.";
}
@Override
public String getCompletedDescription() {
return "You've entered some sort of basement where they've gathered.";
}
},
EISEK_SILLYMODE_QUEST_STAGE_TWO(QuestType.SIDE,
1,
10) {
@Override
public String getName() {
return "Darkest Dungeon";
}
@Override
public String getDescription() {
return "You've followed the basement dwellers back to their basement and decided to have a look inside. Unfortunately they didn't appreciate your trespassing very much and have blocked off the way you came.";
}
@Override
public String getCompletedDescription() {
return "You're nearly at the exit, only one obstacle remains...";
}
},
EISEK_SILLYMODE_QUEST_STAGE_THREE(QuestType.SIDE,
1,
10) {
@Override
public String getName() {
return "Dungeon Cleared";
}
@Override
public String getDescription() {
return "Having defeated the leader of this strange group, all that's left for you to leave.";
}
@Override
public String getCompletedDescription() {
return "You've successfully escaped the dungeon and shown a bunch of nerds who is the boss.";
}
},
220 lines, filled with quest text with a ton of Java enum abuse. Not really code, but at a glance it kinda looks like it, and if you use KLOC as a metric it does add to the number of lines of Java code.
I tried hard not to cherry-pick these examples, either. I just went into the commits page, clicked on a few random commits, and scrolled down. I spotted these in two out of three of the ones I checked.
You can definitively PR XML stuff but nowhere is it written you can't PR java code
You can (
You must be registered to see the links
) but fixing the fundamental issues is a huge time sink.
Don't get me wrong, Inno has improved substantially as a Java dev, but the old code is not doing this project any favors whatsoever. People have been picking at it here and there, but there's a lot to dig through and it's an error-prone process.
I can't emphasize enough how important it is to get the foundation right the first time. A lot of games wind up abandoned simply because the devs coded themselves into a corner and wound up with an unmaintanable mess. LT has survived as long as it has because it's an incredibly unique game, and probably more than a hint of stubbornness.
third: the performance issue i can't really say much but i've use jProfiler and it's really some html webengine stuff doing 60%+ of the time and it's a java thing, not the code.
What you're basically saying here is that if I call a library in an incredibly inefficient way, it's the libraries fault.
So here's the deal. LT calls a fully-featured JavaScript engine to handle simple expression parsing. You can get some pretty significant gains from using a dedicated, lightweight expression parser. Let's be clear: I would be completely on-board with them using JS as a scripting language to decouple some of the game logic from the Java core, but that's not what's happening here.
As for the rest of the web stuff, LT is constantly shoving data into the DOM of a fully-functional web browser to display text. Now I can understand using WebKit to display formatted text. Honestly, it's a pretty convenient way to do that and even if it's slightly slower than using native JavaFX controls it more than makes up for it in simplicity and ease-of-use.
The thing that I doubt anyone can defend is the fact that the game is actually using FIVE webviews at any given time: the main content view, the attributes pane, the map, the buttons at the bottom-left, and the button bar on the bottom. JavaFX has dedicated button and graphics controls, which would have prevented it from having to open up four more instances of WebKit.
Some of the fixes could probably be classified as low-hanging fruit (i.e. switching over the buttons and attribute listing to use JavaFX controls). The map might prove to be a bit more interesting, but axing three WebViews would likely help quite a bit.