252 Commits

Author SHA1 Message Date
acb60e0a08 bump version 2023-09-11 10:51:17 +02:00
dac9b7b8c7 bump assembly version 2023-09-08 11:51:51 +02:00
3338f573c8 update changelog 2023-09-08 11:50:00 +02:00
204d6b8914 rename glaive to hunter
Glaive and Scythe can no longer be distinguished from the bounty claims you get.
2023-09-08 11:49:55 +02:00
4c75515a70 include failed missions in mission format
Since we now have to calculate negative INF with the normal INF (in case both happens), mission formatter now also handles failed missions.
2023-09-08 11:43:05 +02:00
c43c6f742a introduce negative influence
Sometimes missions actually tell us how much negative influence a faction got through secondary influences. We now count this, and present is as negative influence via minuses. Summaries have been updated to reflect this change.
2023-09-08 11:20:49 +02:00
c7a70598c4 don't lose small credit amounts in formatting 2023-09-08 10:21:10 +02:00
cdd7eb33de add settlements for ground CZs to the combat log 2023-08-26 10:53:25 +02:00
dab39b9a4e fix note in FAQ 2023-06-25 19:27:44 +02:00
20c44d41ca fix latest/earliest entry functions 2023-06-18 15:33:07 +02:00
4f339944c6 bump version 2023-06-18 15:25:09 +02:00
ae4e181e7f update changelog 2023-06-18 15:23:38 +02:00
be59588351 improve standard format by adding date range and tool version 2023-06-18 15:22:32 +02:00
1b2a162ac5 add allied to the combat zone object 2023-06-18 15:09:40 +02:00
f490d4ba4d add code to detect which captain/correspondent it was 2023-06-18 15:07:29 +02:00
6b09ec4db3 split captain and correspondent to enemy/allied 2023-06-18 14:55:22 +02:00
d7a1ac5ef2 add another English name 2023-06-13 18:50:58 +02:00
3643dda39e update English mission names 2023-06-01 09:04:28 +02:00
c7be0ef3f5 thargoid bonds are not a system contribution 2023-05-25 18:09:50 +02:00
6a36458026 death only matters on foot 2023-05-25 17:33:59 +02:00
d7dc9bd904 change site to new version number 2023-05-15 19:00:08 +02:00
af66983497 bump version number 2023-05-15 18:59:06 +02:00
3f82e335aa add one more English mission name 2023-05-15 18:56:46 +02:00
77fcbfbba7 update changelog 2023-05-15 18:54:32 +02:00
a45ca9f5bc add option to browse journal location 2023-05-15 18:44:11 +02:00
c9e87958ae implement ignore fleet carrier faction into UI 2023-05-15 18:38:03 +02:00
12b15bb910 fix wrong incomplete type in MarketBuyParser 2023-05-15 18:29:29 +02:00
bec73931b9 add an option to ignore Fleet Carrier faction 2023-05-15 18:15:38 +02:00
b3fca4a63e add faction name for fleet carriers 2023-05-15 17:58:19 +02:00
6a61aa4946 update changelog 2023-05-14 20:22:21 +02:00
b005edc27f don't ignore new system factions
If a factions expands or retreats into a system we see, we won't pick it up if we remain with old data.
2023-05-14 20:20:43 +02:00
8f9f4f3e35 add one more English mission name 2023-05-13 20:19:09 +02:00
7b4176fce5 add drones to the thargoid log 2023-05-13 17:50:26 +02:00
434756695a add information regarding Revenant 2023-05-13 17:47:21 +02:00
a50f0c2313 update description 2023-05-11 20:49:44 +02:00
6dce0116ec update changelog 2023-05-11 20:45:19 +02:00
79914919e5 add combat zone detection through SupercruiseDestinationDrop 2023-05-11 20:43:48 +02:00
c23e8627f6 update instance code 2023-05-11 20:43:30 +02:00
1e36eb3419 fix naming of the event 2023-05-11 20:33:17 +02:00
d49612e7e5 rename class properly 2023-05-11 20:02:53 +02:00
b19a576515 add internal names for combat zones 2023-05-10 09:36:02 +02:00
9ffca16a78 add the new supercruise destination drop event 2023-05-10 09:22:01 +02:00
73a1975964 add payout value for glaive 2023-05-09 21:43:00 +02:00
f9f1842cb7 remove extra bought from log 2023-04-27 16:24:48 +02:00
5487cb3d37 fix log format for murders 2023-04-27 16:24:10 +02:00
e11572a565 update assembly version 2023-04-19 15:52:20 +02:00
16ee7047cd make a release 2023-04-19 15:49:20 +02:00
cedc576b47 mind spelling 2023-04-19 15:08:56 +02:00
58b8ed07c4 fix changelog 2023-04-19 10:15:49 +02:00
ef85f51734 update changelog 2023-04-19 10:15:15 +02:00
00deaf20fc better name market buy/market sold 2023-04-19 10:13:35 +02:00
5fe652f98c add profit to search and rescue 2023-04-19 10:13:02 +02:00
8cf8d7f529 change one line discord log to only show summaries 2023-04-19 10:06:10 +02:00
fce534c88f remove old ico from source files 2023-04-19 09:38:04 +02:00
25338c8754 bump version 2023-04-19 09:29:21 +02:00
139cbd05f8 update changelog 2023-04-19 09:27:57 +02:00
b0302572f8 forgot to load the setting 2023-04-19 09:25:36 +02:00
87314d0258 we still want profit info, just not BGS info 2023-04-19 09:19:37 +02:00
f4bbd3df2b add GUI elements for ignoring market buy 2023-04-19 09:17:06 +02:00
d6acbda55c add option to ignore market buy 2023-04-19 09:06:54 +02:00
5c9d9c9153 correct black market trade by properly setting StationOwner 2023-04-19 09:01:32 +02:00
da3a355695 provide station faction via Location entry 2023-04-19 08:37:22 +02:00
5224dd4555 split out some classes to avoid clutter 2023-04-19 08:31:23 +02:00
15fda7692e format credits with millions 2023-04-11 19:17:29 +02:00
4cd0c02c67 write config file in indented JSON 2023-03-29 18:41:10 +02:00
e39dd6ffda add setting to ignore influence support 2023-03-29 18:40:36 +02:00
f666a2c7c5 add additional english names 2023-03-27 21:34:13 +02:00
8574b74494 add more English mission names 2023-03-25 12:21:34 +01:00
1255bf9fa5 add two more English mission names 2023-03-18 00:57:41 +01:00
781bb7a209 more thargoid payout changes 2023-03-18 00:56:16 +01:00
24817a02a9 add more English mission names 2023-03-08 15:45:24 +01:00
d00c8d5c06 allow possibility to ignore influence support 2023-03-03 15:59:41 +01:00
b588050fb4 ignoring exo biology is now handled in the parser 2023-03-02 20:10:49 +01:00
f9eb0d34f9 introduce parser options
And use it to ignore exo biology data.
2023-03-02 20:09:19 +01:00
2a393809fc add additional mission names 2023-03-01 13:43:13 +01:00
7ee734bc33 bugfix release 2023-02-26 22:16:01 +01:00
5799b3ed16 fix summary for failed missions 2023-02-26 22:03:31 +01:00
a13e8446d2 fix summary for AX combat zones 2023-02-26 22:01:13 +01:00
0665e64459 fix VH type 2023-02-26 21:57:45 +01:00
d8ac2a7ee7 grade can be null 2023-02-26 21:57:24 +01:00
1a8c163fc4 update readmes to include mahapps 2023-02-26 10:24:01 +01:00
7b7fdf0ceb update README 2023-02-26 10:21:55 +01:00
bf86ce3fe8 update documentation 2023-02-26 10:20:11 +01:00
bbf3d6c6a4 add another mission name 2023-02-26 10:10:48 +01:00
d1c232df9d if there is a bug, use a default skin 2023-02-26 10:09:50 +01:00
82281ecf1b English is hard 2023-02-25 18:14:00 +01:00
7bf2b028c3 add link to homepage & source code to window 2023-02-25 18:12:33 +01:00
d2300fa088 update package information 2023-02-25 17:54:42 +01:00
32d7bccadc update changelog 2023-02-25 17:51:23 +01:00
831bf38e78 sort combat zones 2023-02-25 17:48:01 +01:00
e864db8488 add english names 2023-02-25 17:41:51 +01:00
0250878ee7 add more english names 2023-02-25 11:00:25 +01:00
5120c7991f implement a summary line
For Pony's bot, implement a summary line that just shows the highlights of the log
2023-02-23 21:44:55 +01:00
3c1abe5e8c change colour 2023-02-23 20:57:43 +01:00
637b4f85d0 use brighter colour 2023-02-23 20:57:28 +01:00
53bf0d22b8 add custom themes for NovaNavy & Salus 2023-02-23 20:55:17 +01:00
1c0c864e62 remove exo biology from reports
Multiple confirmations tell that the exo biology donations don't affect BGS at all.
2023-02-22 20:51:51 +01:00
52aa2706c0 switch to MahApps controls and style
Why didn't I find this sooner?
2023-02-22 20:39:40 +01:00
fd10b86c79 add more mission names 2023-02-22 18:25:12 +01:00
2f8d32f57a add some more mission names 2023-02-13 17:26:20 +01:00
926150cd18 update webpage with releases from CodeBerg 2023-02-04 16:15:42 +01:00
29dc6e9cba bump package version 2023-02-04 16:02:29 +01:00
aeb16c675b update changelog 2023-02-04 16:01:19 +01:00
00f94ef939 update documentation to point to codeberg 2023-02-04 15:59:50 +01:00
785efad4c0 add updated values post 14.02 for thargoid kills 2023-02-04 15:55:55 +01:00
3a968a7c55 fix compilation error 2023-02-04 15:55:45 +01:00
44d5a2b91c add a few more mission names 2023-01-10 09:27:33 +01:00
69b6e8b306 add a whole set of new mission names 2023-01-10 09:25:04 +01:00
3bfe599c11 fix a "bug" thanks to Shakaka 2023-01-03 16:33:29 +01:00
0b47b7331e fix bug with failed missions 2022-12-28 10:43:45 +01:00
2fae724bd2 condense mission fails down 2022-12-28 10:41:26 +01:00
132d090fd5 add API to determine on foot missions 2022-12-28 09:53:02 +01:00
9d686bea61 add mining mission name 2022-12-28 09:51:34 +01:00
78c2636a40 update FAQ 2022-12-20 18:49:29 +01:00
4549413b00 add warning for empty mission faction 2022-12-20 18:48:00 +01:00
f32f1f7dff update web page 2022-12-20 18:43:18 +01:00
86f2356f35 release the new version 2022-12-20 18:42:56 +01:00
6608f33d8d update versions 2022-12-20 18:42:27 +01:00
42987cdb12 update changelog 2022-12-20 18:42:20 +01:00
4f6376d39b remove old code 2022-12-20 18:38:33 +01:00
273d151358 add more mission names 2022-12-20 18:37:18 +01:00
a9ce5be266 fix mission fails 2022-12-20 18:33:09 +01:00
3bd0cc1055 update project data 2022-12-18 10:21:18 +01:00
1d1ea3f5ef update changelog 2022-12-18 10:20:03 +01:00
b12ab287b6 change date in NONA discord log to UTC 2022-12-18 10:15:17 +01:00
9deafa6653 fix incomplete transactions not having a CompletedAt 2022-12-18 10:13:35 +01:00
efbbaa8abf make system and faction bold 2022-12-16 21:48:16 +01:00
b96e40299d make pilot's federation combat bonds system contribution 2022-12-15 22:52:50 +01:00
194add4436 add more English mission names 2022-12-15 22:44:32 +01:00
02286e19a8 fix manual parsing with new time adjustments 2022-12-15 22:44:23 +01:00
b4e6b1b46c bump version in project file 2022-12-14 20:47:17 +01:00
e9b79a8c7a update changelog 2022-12-14 20:23:13 +01:00
86d27fb2a3 improve organic data formatting 2022-12-14 20:18:20 +01:00
271b151a72 fix newlines in mission log 2022-12-14 20:05:57 +01:00
e75e0773fd fiddle a bit more with the margins 2022-12-14 19:35:48 +01:00
ba9b108e5e add a toggle all 2022-12-14 19:28:23 +01:00
93a76bc429 tighten up margins 2022-12-14 19:09:59 +01:00
0cb8641056 add a button to reset time 2022-12-14 19:07:05 +01:00
c2082df6f1 give the splitter a colour 2022-12-14 18:53:03 +01:00
65d12cb052 use datetimepicker for more carefull transaction picking 2022-12-14 18:44:39 +01:00
44410e86c8 fix a bug where mission support wasn't shown 2022-12-14 18:19:46 +01:00
5c7580a3c0 allow resizing of the two fields 2022-12-14 18:17:25 +01:00
f0692c3936 apparently also medium combat zones can have two objectives 2022-12-14 17:40:25 +01:00
a181a3cdd2 fix readme 2022-12-11 12:37:14 +01:00
40d93e7c84 time for a new release 2022-12-11 12:35:27 +01:00
db192748b0 update changelog 2022-12-11 12:31:36 +01:00
a43ade2f3c adjust detection of high combat zones 2022-12-11 12:30:02 +01:00
a6b16fd5b8 commander entry happens when you relog
Use it to discern combat zone relogs.
2022-12-11 12:29:32 +01:00
99373b47c0 add repo README 2022-12-10 11:53:41 +01:00
f5f85586ec update changelog 2022-12-10 11:51:19 +01:00
ba4936ccb0 add more mission names 2022-12-10 10:03:31 +01:00
44774c5cad fix detection of medium ground combat zones 2022-12-10 09:52:48 +01:00
8d3d48d846 refactor to only filter transactions based on dates, not the entries 2022-12-09 17:52:03 +01:00
a3b54b1326 add DateTime property variant for CompletedAt 2022-12-09 17:51:46 +01:00
bf43262a8e add support for DropshipDeploy 2022-12-09 17:36:26 +01:00
ef8e71cd8a add more mission names for thargoid war 2022-12-08 19:06:59 +01:00
bd0d5cf0b6 update webpage 2022-12-07 23:25:45 +01:00
365c34bdb0 update changelog 2022-12-07 23:22:38 +01:00
7fa19c0733 add comment about old Orthrus value 2022-12-07 23:22:28 +01:00
ea07c4dbcb add number of passengers 2022-12-07 23:21:36 +01:00
6f9d3a64ff count all thargoid war missions as system contributions 2022-12-07 23:08:31 +01:00
abdaca2524 add a one line discord log for simple logging 2022-12-07 23:00:38 +01:00
1f673970ee fix formatting of combat zones 2022-12-07 22:43:24 +01:00
2608aeab6f don't bother detecting combat zones if we don't have enough data 2022-12-07 22:41:10 +01:00
b84d028115 add mission massacre wing for scouts 2022-12-07 22:39:14 +01:00
123fa15884 improve detection of combat zones by listening for messages 2022-12-07 16:42:44 +01:00
43ae93658f add receive text entry 2022-12-07 16:06:07 +01:00
8dea9fd094 remove unused includes 2022-12-07 15:46:31 +01:00
8a0d21d039 remove misleading comment 2022-12-07 15:43:07 +01:00
c20bc3d0bf update payouts for Orthrus in U14.1 2022-12-07 15:42:36 +01:00
cb8698dd42 thargoid combat zones are called "AX" combat zones ingame 2022-12-03 20:42:38 +01:00
f5f6d538f0 Orthrus has been confirmed with 50m payout 2022-12-03 20:37:39 +01:00
eaeaa5e0b4 add station state to the journal 2022-12-03 20:34:16 +01:00
3df82af239 update logs 2022-12-03 16:37:43 +01:00
0e92ee8ab4 only thargoid war missions are system contributions 2022-12-03 16:34:54 +01:00
72cb379c95 update nuget package info 2022-12-03 15:13:38 +01:00
84dfd73283 thargoid kills also contribute to the whole system 2022-12-03 15:05:09 +01:00
22d0bedf53 introduce the concept of system contributions 2022-12-03 15:01:46 +01:00
1e73c6b7eb move stuff further in to avoid scroll bars 2022-12-03 14:49:02 +01:00
661a4672d9 add support for thargoid combat zones 2022-12-03 14:47:31 +01:00
67966ca27a add code to detect thargoid combat zones 2022-12-03 14:42:32 +01:00
914aa43d33 lay ground work for supporting thargoid combat zones 2022-12-03 14:33:09 +01:00
8d72808fbf refactor missions a bit 2022-12-03 14:16:45 +01:00
ac3d1cd8b9 add thargoid passenger mission 2022-12-03 13:55:14 +01:00
634ba657b4 didn't hide the expander for objectives on ground combat zones 2022-12-03 11:24:44 +01:00
6b2bef0d30 update main page 2022-11-29 18:28:58 +01:00
d6b86d28aa update screenshot after declutter 2022-11-29 18:17:21 +01:00
bfae0adcae declutter GUI with expanders 2022-11-29 18:15:30 +01:00
454af4ad8f fix profit adjustment window 2022-11-29 18:05:08 +01:00
7f73159bb0 update changelog 2022-11-29 17:53:38 +01:00
0820998d3e fix formatting for combat zones 2022-11-29 17:52:48 +01:00
f14982f37a change "on foot" to "ground" 2022-11-29 17:52:27 +01:00
f0977b774b remove "Amount" from combat zones 2022-11-29 17:46:17 +01:00
04a839417e move combat zone adding to the tree view 2022-11-29 17:42:20 +01:00
93359321d6 remove some no longer used dependencies 2022-11-29 17:28:18 +01:00
6bbc063c25 update changelog 2022-11-29 17:22:58 +01:00
6fe6f5e26f set version to 0.2.0 2022-11-29 17:20:34 +01:00
9f1065c6ea shameless self plug with Salus icon 2022-11-29 17:09:45 +01:00
674757d734 add warning about legacy ED 2022-11-29 16:39:20 +01:00
712c416725 store in the transaction whether it was done in legacy or live 2022-11-29 16:30:46 +01:00
91bca70168 ground combat zones don't have objectives 2022-11-28 20:08:04 +01:00
800ac73331 add GUI for optional objectives in combat zones 2022-11-28 20:06:14 +01:00
d79ee5a153 fix payouts for medium combat zones 2022-11-28 19:49:52 +01:00
f683bc372e handle capshipbond entries 2022-11-28 19:47:41 +01:00
12ba6d4d96 add capshipbond entry 2022-11-28 19:43:51 +01:00
dd1d7ff091 store optional events in combat zone 2022-11-28 19:40:33 +01:00
65fec2cd1e two special warzone NPCs means high conflict zone 2022-11-28 19:28:55 +01:00
bb74d72b13 add properties for IsLive and IsLegacy 2022-11-28 13:32:54 +01:00
d2aadf9882 fix unit test 2022-11-28 11:00:11 +01:00
6b14909335 move elements a bit further in 2022-11-26 17:40:03 +01:00
db67ef21fa make system name and faction name bold 2022-11-26 17:39:31 +01:00
f3134d36d4 fix influence support 2022-11-26 17:32:08 +01:00
1a53a3ba8d add documentation about combat zones 2022-11-26 17:27:18 +01:00
c7e2f352f2 cleanup include 2022-11-26 17:18:11 +01:00
f7c3a1bb41 fix combat zone dialog 2022-11-26 17:15:23 +01:00
2648b1390b its On Foot 2022-11-26 17:12:57 +01:00
aad688026e its On Foot not OnFoot 2022-11-26 17:12:42 +01:00
a451392e93 remove adjust profit window 2022-11-26 17:10:52 +01:00
10351af7f9 also allow changing of profit through tree view 2022-11-26 17:09:01 +01:00
697cbc2bc6 collate combat zones 2022-11-26 16:27:51 +01:00
0cc8a1f29a FSDJump is also an instance change for combat zones 2022-11-26 16:22:35 +01:00
22396c9b22 add functionality to change grade of combat zones in the tree 2022-11-26 16:19:55 +01:00
8d95a42698 don't add faction for combat zone 2022-11-26 01:29:50 +01:00
aa7f0bcd1a collate murders 2022-11-26 00:39:06 +01:00
6e5f8f504f factions for onfoot murders are right there 2022-11-26 00:30:42 +01:00
e01d3a869d if player targeted warzone NPCs assume its medium or higher 2022-11-25 23:54:28 +01:00
1da4549e2c fix completed at for combat zones 2022-11-25 23:06:08 +01:00
0be9791abc reintroduce cleanup function 2022-11-25 18:06:30 +01:00
027f8a892d add function to determine whether entry is relevant 2022-11-25 18:06:19 +01:00
32e5898b02 collate vouchers together 2022-11-25 17:56:43 +01:00
34934c82a6 add new test data 2022-11-25 17:56:35 +01:00
5a8ea2940c include Hellriders bug as BGS oddity 2022-11-25 17:45:13 +01:00
a13a708c22 shorted mission format 2022-11-25 17:45:03 +01:00
fbd5a66156 fix ISO8601 parsing of datetime 2022-11-25 17:36:41 +01:00
0be0896e91 fix handling of incomplete transactions 2022-11-25 17:28:10 +01:00
f8559bbe06 update index to denote we need .NET 7 2022-11-25 17:22:05 +01:00
987d9c5f49 delete upgrade assistant files 2022-11-25 17:19:48 +01:00
24f3897582 update links 2022-11-25 17:18:40 +01:00
afa4e0d536 update index 2022-11-25 17:17:04 +01:00
1d8f03200b update documentation for newest version 2022-11-25 17:16:58 +01:00
955a108f2e add a highly rudimentary combat zone detector 2022-11-25 17:13:17 +01:00
fe28a4d17d remove unused folder 2022-11-25 16:09:41 +01:00
dc9e654351 remove collate function again 2022-11-25 16:04:21 +01:00
c2037f28e2 repair automatic refresh 2022-11-25 16:03:32 +01:00
e6e0233e6d handle incomplete transactions 2022-11-25 15:58:06 +01:00
1a6183ba9d incomplete transactions should not be internal 2022-11-25 15:57:59 +01:00
3a5d0a90ef improve reporting of combat vouchers and thargoid kills 2022-11-25 15:51:01 +01:00
8514331701 add support for thargoid vouchers 2022-11-25 15:48:00 +01:00
e8de768d01 implement thargoid bonds for pilots federation 2022-11-25 15:14:50 +01:00
61f1b10f29 improve log header if no system is known 2022-11-25 14:53:04 +01:00
e0e7cb5792 add thargoid formatter for logs 2022-11-25 14:48:59 +01:00
5f9fff3922 unknown system is better than no system 2022-11-25 14:39:45 +01:00
120 changed files with 4455 additions and 8120 deletions

View File

@@ -9,6 +9,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EDPlayerJournalTests", "EDP
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EliteBGS", "EliteBGS\EliteBGS.csproj", "{14B03115-B9D5-4006-A993-9A5069BB172E}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EliteBGS", "EliteBGS\EliteBGS.csproj", "{14B03115-B9D5-4006-A993-9A5069BB172E}"
EndProject EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{1381D5DE-139E-4FEC-BDC4-A3FDADEAEE5C}"
ProjectSection(SolutionItems) = preProject
README.md = README.md
EndProjectSection
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU

View File

@@ -16,6 +16,22 @@ the game:
} }
``` ```
## Bounty vouchers show up twice
Sometimes bounty vouchers are redeemed twice. This happens because FDev writes out two
`RedeemVoucher` entries with the same values:
```
{ "timestamp":"2022-11-20T14:30:30Z", "event":"RedeemVoucher", "Type":"bounty", "Amount":3197549,
"Factions":[ { "Faction":"Nova Paresa", "Amount":3197549 } ] }
{ "timestamp":"2022-11-20T14:30:34Z", "event":"RedeemVoucher", "Type":"bounty", "Amount":4785831,
"Factions":[ { "Faction":"", "Amount":723175 }, { "Faction":"Nova Paresa", "Amount":3197549 }, { "Faction":"Prismatic Imperium", "Amount":4062656 } ] }
```
Two separate events (apart by four seconds) containing the same entries twice. This
happens when you turn in one bounty manually, and then click the "redeem all" button
again, which contains the already turned in vouchers.
## Combat zones ## Combat zones
Combat zones receive no mention within the player journal whatsoever. There is no event Combat zones receive no mention within the player journal whatsoever. There is no event

View File

@@ -1,10 +1,11 @@
using System.Linq; using System.Text;
using System.Text;
using EDPlayerJournal.Entries; using EDPlayerJournal.Entries;
namespace EDPlayerJournal.BGS; namespace EDPlayerJournal.BGS;
public class Cartographics : Transaction { public class Cartographics : Transaction {
public Cartographics() { }
public Cartographics(MultiSellExplorationDataEntry e) { public Cartographics(MultiSellExplorationDataEntry e) {
Entries.Add(e); Entries.Add(e);
} }

View File

@@ -3,15 +3,93 @@ using System.Linq;
namespace EDPlayerJournal.BGS; namespace EDPlayerJournal.BGS;
public class CombatZone : Transaction { public class CombatZone : Transaction {
public string Type { get; set; } = ""; /// <summary>
public string Grade { get; set; } = ""; /// Type, either on foot or ship
public int Amount { get; set; } = 0; /// </summary>
public DateTime Completed { get; set; } = DateTime.UtcNow; public string Type { get; set; } = CombatZones.ShipCombatZone;
public override string CompletedAt { /// <summary>
/// Difficulty type, low, medium or high. Null means unknown.
/// </summary>
public string? Grade { get; set; }
/// <summary>
/// Whether spec ops were won.
/// </summary>
public bool? SpecOps { get; set; }
/// <summary>
/// Whether allied captain objective was won
/// </summary>
public bool? AlliedCaptain { get; set; }
/// <summary>
/// Whether enemy captain objective was won
/// </summary>
public bool? EnemyCaptain { get; set; }
/// <summary>
/// Whether the allied correspondent objective was won
/// </summary>
public bool? AlliedCorrespondent { get; set; }
/// <summary>
/// Whether the enemy correspondent objective was won
/// </summary>
public bool? EnemyCorrespondent { get; set; }
/// <summary>
/// Whether cap ship objective was won
/// </summary>
public bool? CapitalShip { get; set; }
/// <summary>
/// If we have a combat zone, this might point to the settlement
/// in question.
/// </summary>
public string? Settlement { get; set; }
/// <summary>
/// How many optional objectives were completed?
/// </summary>
public int OptionalObjectivesCompleted {
get { get {
return Completed.ToString("dd.MM.yyyy HH:mm UTC"); if (IsGround) {
return 0;
} }
return new List<bool?>() {
SpecOps,
AlliedCaptain,
EnemyCaptain,
AlliedCorrespondent,
EnemyCorrespondent,
CapitalShip
}
.Where(x => x != null && x == true)
.Count()
;
}
}
/// <summary>
/// Returns true if it is an on foot/ground combat zone
/// </summary>
public bool IsGround {
get { return string.Compare(Type, CombatZones.GroundCombatZone) == 0; }
}
/// <summary>
/// Returns true if it is an on foot combat zone
/// </summary>
public bool IsShip {
get { return string.Compare(Type, CombatZones.ShipCombatZone) == 0; }
}
/// <summary>
/// Returns true if it is a thargoid combat zone
/// </summary>
public bool IsThargoid {
get { return string.Compare(Type, CombatZones.AXCombatZone) == 0; }
} }
public override int CompareTo(Transaction? obj) { public override int CompareTo(Transaction? obj) {
@@ -36,7 +114,10 @@ public class CombatZone : Transaction {
} }
public override string ToString() { public override string ToString() {
return string.Format("Won {0} x {1} {2} Combat Zone(s) for {3}", if (!string.IsNullOrEmpty(Grade)) {
Amount, Grade, Type, Faction); return string.Format("Won {0} {1} Combat Zone", Grade, Type);
} else {
return string.Format("Won {0} Combat Zone", Type);
}
} }
} }

View File

@@ -1,12 +1,16 @@
namespace EDPlayerJournal.BGS; using EDPlayerJournal.Entries;
internal class IncompleteTransaction : Transaction {
namespace EDPlayerJournal.BGS;
public class IncompleteTransaction : Transaction {
public Transaction? UnderlyingTransaction { get; set; } = null; public Transaction? UnderlyingTransaction { get; set; } = null;
public string Reason { get; set; } = ""; public string Reason { get; set; } = "";
public IncompleteTransaction() { } public IncompleteTransaction() { }
public IncompleteTransaction(Transaction? underlying, string reason) { public IncompleteTransaction(Transaction? underlying, string reason, Entry entry) {
UnderlyingTransaction = underlying; UnderlyingTransaction = underlying;
Reason = reason; Reason = reason;
Entries.Add(entry);
} }
} }

View File

@@ -9,15 +9,24 @@ namespace EDPlayerJournal.BGS;
/// faction to another. Both sometimes gain influence. /// faction to another. Both sometimes gain influence.
/// </summary> /// </summary>
public class InfluenceSupport : Transaction { public class InfluenceSupport : Transaction {
public string Influence { get; set; } = ""; public MissionInfluence? Influence { get; set; } = null;
/// <summary>
/// Relevant mission completed entry
/// </summary>
public MissionCompletedEntry? RelevantMission { get; set; } public MissionCompletedEntry? RelevantMission { get; set; }
public override string CompletedAt { /// <summary>
/// Mission information
/// </summary>
public Mission? Mission { get; set; }
public override DateTime? CompletedAtDateTime {
get { get {
if (RelevantMission == null) { if (RelevantMission == null) {
return ""; return null;
} }
return RelevantMission.Timestamp.ToString("dd.MM.yyyy hh:mm UTC"); return RelevantMission.Timestamp;
} }
} }
@@ -26,7 +35,7 @@ public class InfluenceSupport : Transaction {
string? missionname; string? missionname;
if (RelevantMission != null && RelevantMission.Mission != null) { if (RelevantMission != null && RelevantMission.Mission != null) {
missionname = RelevantMission.Mission.LocalisedName; missionname = RelevantMission.Mission.FriendlyName;
} else { } else {
missionname = "UNKNOWN MISSION"; missionname = "UNKNOWN MISSION";
} }
@@ -37,7 +46,7 @@ public class InfluenceSupport : Transaction {
builder.AppendFormat("Influence gained from \"{0}\": \"{1}\"", builder.AppendFormat("Influence gained from \"{0}\": \"{1}\"",
missionname, missionname,
string.IsNullOrEmpty(Influence) ? "NONE" : Influence Influence == null ? "NONE" : Influence.TrendAdjustedInfluence
); );
return builder.ToString(); return builder.ToString();

View File

@@ -1,54 +1,86 @@
using System.Text; using System.Collections.Generic;
using System.Text;
using EDPlayerJournal.Entries; using EDPlayerJournal.Entries;
namespace EDPlayerJournal.BGS; namespace EDPlayerJournal.BGS;
public class MissionCompleted : Transaction { public class MissionCompleted : Transaction {
public MissionCompletedEntry? CompletedEntry { get; set; }
public MissionAcceptedEntry? AcceptedEntry { get; set; }
public Mission? Mission { get; set; }
public MissionCompleted() { } public MissionCompleted() { }
public MissionCompleted(MissionCompletedEntry e) {
Entries.Add(e); public override DateTime? CompletedAtDateTime {
get {
if (CompletedEntry == null) {
return null;
}
return CompletedEntry.Timestamp;
}
} }
public string MissionName { public string MissionName {
get { get {
MissionCompletedEntry? c = Entries[0] as MissionCompletedEntry; if (CompletedEntry == null || CompletedEntry.Mission == null) {
if (c == null || c.Mission == null) {
return ""; return "";
} }
return c.Mission.FriendlyName; return CompletedEntry.Mission.FriendlyName;
} }
} }
public string Influence { public string Influence {
get { get {
MissionCompletedEntry? e = (Entries[0] as MissionCompletedEntry); if (CompletedEntry == null || Faction == null || CompletedEntry.Mission == null) {
if (e == null || Faction == null || e.Mission == null) {
return ""; return "";
} }
return (e.Mission.GetInfluenceForFaction(Faction, SystemAddress) ?? ""); return string.Join("",
CompletedEntry
.Mission
.GetInfluenceForFaction(Faction, SystemAddress)
.Select(x => x.Influence)
.ToArray()
)
;
}
}
/// <summary>
/// Rescue missions contribute to the system.
/// </summary>
public override bool SystemContribution {
get {
if (Mission == null || Mission.Name == null) {
return false;
}
// If the mission starts with the name for thargoid war mission
// names, we assume its a system contribution
if (Mission.Name.Contains("Mission_TW_")) {
return true;
}
return false;
} }
} }
public override string ToString() { public override string ToString() {
if (Faction == null || Entries.Count <= 0) { if (Faction == null || CompletedEntry == null || CompletedEntry.Mission == null) {
return ""; return "";
} }
StringBuilder builder = new StringBuilder(); StringBuilder builder = new StringBuilder();
var entry = Entries[0] as MissionCompletedEntry; var influence = CompletedEntry.Mission.GetInfluenceForFaction(Faction, SystemAddress);
if (entry == null || entry.Mission == null) {
return "";
}
var influence = entry.Mission.GetInfluenceForFaction(Faction, SystemAddress);
builder.AppendFormat("{0}", MissionName); builder.AppendFormat("{0}", MissionName);
if (influence != "") { if (influence != null && influence.Length > 0) {
builder.AppendFormat(", Influence: {0}", influence); builder.AppendFormat(", Influence: {0}",
influence.Select(x => x.InfluenceAmount).Sum()
);
} }
return builder.ToString(); return builder.ToString();

View File

@@ -4,16 +4,28 @@ using EDPlayerJournal.Entries;
namespace EDPlayerJournal.BGS; namespace EDPlayerJournal.BGS;
public class MissionFailed : Transaction { public class MissionFailed : Transaction {
public MissionFailedEntry? Failed { get; set; } public MissionFailedEntry? Failed { get; set; }
public MissionAcceptedEntry? Accepted { get; set; } public Mission? Mission { get; set; }
public MissionFailed() { } public MissionFailed() { }
public MissionFailed(MissionAcceptedEntry accepted) { public MissionFailed(MissionFailedEntry failed) {
if (accepted.Mission == null) { Entries.Add(failed);
throw new Exception("Mission cannot be null"); Failed = failed;
}
/// <summary>
/// Returns the amount of influence generated by failing this mission. The
/// system returns no influence for one INF missions, but sadly also for
/// when the influence had no affect at all (i.e. during a war).
/// </summary>
public long InfluenceAmount {
get {
if (Mission == null || string.IsNullOrEmpty(Mission.Influence)) {
return -1;
} else {
return Mission.Influence.Length * -1;
}
} }
Accepted = accepted;
Faction = accepted.Mission.Faction;
} }
public override int CompareTo(Transaction? other) { public override int CompareTo(Transaction? other) {
@@ -39,20 +51,15 @@ public class MissionFailed : Transaction {
return -1; return -1;
} }
public int Amount {
get { return Entries.Count + 1; }
}
public override string ToString() { public override string ToString() {
StringBuilder builder = new StringBuilder(); StringBuilder builder = new StringBuilder();
if (Failed == null || Accepted == null) { if (Failed == null || Mission == null) {
return ""; return "";
} }
builder.AppendFormat("{0}x Mission failed: \"{1}\"", builder.AppendFormat(" Mission failed: \"{0}\"",
Amount, Mission?.FriendlyName
Failed?.Mission?.FriendlyName
); );
return builder.ToString(); return builder.ToString();

View File

@@ -4,6 +4,8 @@ using EDPlayerJournal.Entries;
namespace EDPlayerJournal.BGS; namespace EDPlayerJournal.BGS;
public class OrganicData : Transaction { public class OrganicData : Transaction {
public OrganicData() { }
public OrganicData(SellOrganicDataEntry e) { public OrganicData(SellOrganicDataEntry e) {
Entries.Add(e); Entries.Add(e);
} }
@@ -36,6 +38,8 @@ public class OrganicData : Transaction {
/* Selling organic data only helps the controlling faction, just like /* Selling organic data only helps the controlling faction, just like
* selling cartographic data. * selling cartographic data.
*
* Right now: Organic data helps no one.
*/ */
public override bool OnlyControllingFaction => true; public override bool OnlyControllingFaction => true;
} }

View File

@@ -0,0 +1,15 @@
using EDPlayerJournal.Entries;
namespace EDPlayerJournal.BGS.Parsers;
internal class ApproachSettlementParser : ITransactionParserPart {
public void Parse(Entry entry, TransactionParserContext context, TransactionParserOptions options, TransactionList transactions) {
ApproachSettlementEntry? approach = entry as ApproachSettlementEntry;
if (approach == null || string.IsNullOrEmpty(approach.Name)) {
return;
}
context.Settlement = approach.Name;
}
}

View File

@@ -0,0 +1,44 @@
using EDPlayerJournal.Entries;
namespace EDPlayerJournal.BGS;
internal class MarketBuyParser : ITransactionParserPart {
public void Parse(Entry e, TransactionParserContext context, TransactionParserOptions options, TransactionList transactions) {
MarketBuyEntry? entry = e as MarketBuyEntry;
if (entry == null) {
throw new NotImplementedException();
}
context.BoughtCargo(entry.Type, entry.BuyPrice);
// We still want the information on buy price for profit,
// but if the option is on, we don't care for parsing it
// further.
// TODO: might be wise to split this parser into two; one for
// determining profit, the other for the BGS information
if (options.IgnoreMarketBuy) {
return;
}
if (context.StationOwner == null) {
transactions.AddIncomplete(
new BuyCargo(),
"Could not discern the station owner for market buy.",
e);
return;
}
// Ignore if its a fleet carrier faction.
if (string.Compare(context.StationOwner, Factions.FleetCarrier, StringComparison.OrdinalIgnoreCase) == 0 &&
options.IgnoreFleetCarrierFaction) {
return;
}
transactions.Add(new BuyCargo(entry) {
System = context.CurrentSystem,
Station = context.CurrentStation,
Faction = context.StationOwner,
IsLegacy = context.IsLegacy,
});
}
}

View File

@@ -0,0 +1,45 @@
using EDPlayerJournal.Entries;
namespace EDPlayerJournal.BGS;
internal class MarketSellParser : ITransactionParserPart {
public void Parse(Entry e, TransactionParserContext context, TransactionParserOptions options, TransactionList transactions) {
long profit = 0;
if (context.StationOwner == null) {
transactions.AddIncomplete(
new SellCargo(),
"Could not discern the station owner market sell.",
e);
return;
}
// Ignore if its a fleet carrier faction.
if (string.Compare(context.StationOwner, Factions.FleetCarrier, StringComparison.OrdinalIgnoreCase) == 0 &&
options.IgnoreFleetCarrierFaction) {
return;
}
MarketSellEntry? entry = e as MarketSellEntry;
if (entry == null) {
throw new NotImplementedException();
}
if (entry.Type == null) {
throw new InvalidJournalEntryException("market sell contains no cargo type");
}
if (context.BuyCost.ContainsKey(entry.Type)) {
long avg = context.BuyCost[entry.Type];
profit = (long)entry.TotalSale - (avg * entry.Count);
}
transactions.Add(new SellCargo(entry) {
System = context.CurrentSystem,
Station = context.CurrentStation,
Faction = context.StationOwner,
Profit = profit,
IsLegacy = context.IsLegacy,
});
}
}

View File

@@ -0,0 +1,33 @@
using EDPlayerJournal.Entries;
namespace EDPlayerJournal.BGS;
internal class MultiSellExplorationDataParser : ITransactionParserPart {
public void Parse(Entry e, TransactionParserContext context, TransactionParserOptions options, TransactionList transactions) {
MultiSellExplorationDataEntry? entry = e as MultiSellExplorationDataEntry;
if (entry == null) {
throw new NotImplementedException();
}
if (context.StationOwner == null) {
transactions.AddIncomplete(
new Cartographics(),
"Could not discern the station owner for cartographics sell.",
e);
return;
}
// Ignore if its a fleet carrier faction.
if (string.Compare(context.StationOwner, Factions.FleetCarrier, StringComparison.OrdinalIgnoreCase) == 0 &&
options.IgnoreFleetCarrierFaction) {
return;
}
transactions.Add(new Cartographics(entry) {
System = context.CurrentSystem,
Station = context.CurrentStation,
Faction = context.StationOwner,
IsLegacy = context.IsLegacy,
});
}
}

View File

@@ -0,0 +1,33 @@
using EDPlayerJournal.Entries;
namespace EDPlayerJournal.BGS;
internal class SellExplorationDataParser : ITransactionParserPart {
public void Parse(Entry e, TransactionParserContext context, TransactionParserOptions options, TransactionList transactions) {
SellExplorationDataEntry? entry = e as SellExplorationDataEntry;
if (entry == null) {
throw new NotImplementedException();
}
if (context.StationOwner == null) {
transactions.AddIncomplete(
new Cartographics(),
"Could not discern the station owner for cartographics sell.",
e);
return;
}
// Ignore if its a fleet carrier faction.
if (string.Compare(context.StationOwner, Factions.FleetCarrier, StringComparison.OrdinalIgnoreCase) == 0 &&
options.IgnoreFleetCarrierFaction) {
return;
}
transactions.Add(new Cartographics(entry) {
System = context.CurrentSystem,
Station = context.CurrentStation,
Faction = context.StationOwner,
IsLegacy = context.IsLegacy,
});
}
}

View File

@@ -0,0 +1,38 @@
using EDPlayerJournal.Entries;
namespace EDPlayerJournal.BGS;
internal class SellOrganicDataParser : ITransactionParserPart {
public void Parse(Entry e, TransactionParserContext context, TransactionParserOptions options, TransactionList transactions) {
// If exo biology is ignored, simply do nothing
if (options.IgnoreExoBiology) {
return;
}
SellOrganicDataEntry? entry = e as SellOrganicDataEntry;
if (entry == null) {
throw new NotImplementedException();
}
if (context.StationOwner == null) {
transactions.AddIncomplete(
new OrganicData(),
"Could not discern the station owner for organic data sell.",
e);
return;
}
// Ignore if its a fleet carrier faction.
if (string.Compare(context.StationOwner, Factions.FleetCarrier, StringComparison.OrdinalIgnoreCase) == 0 &&
options.IgnoreFleetCarrierFaction) {
return;
}
transactions.Add(new OrganicData(entry) {
System = context.CurrentSystem,
Station = context.CurrentStation,
Faction = context.StationOwner,
IsLegacy = context.IsLegacy,
});
}
}

View File

@@ -0,0 +1,65 @@
using EDPlayerJournal.Entries;
namespace EDPlayerJournal.BGS;
/// <summary>
/// With ship targeted we might find out to which faction a given NPC belonged. This is
/// useful later when said NPC gets killed or murdered.
/// </summary>
internal class ShipTargetedParser : ITransactionParserPart {
public void Parse(Entry e, TransactionParserContext context, TransactionParserOptions options, TransactionList transactions) {
ShipTargetedEntry? entry = e as ShipTargetedEntry;
if (entry == null) {
throw new NotImplementedException();
}
// Scan happens in stages, and sometimes this information is not known
// yet. Do now throw an error, this is expected behaviour.
if (!string.IsNullOrEmpty(entry.PilotNameLocalised) &&
!string.IsNullOrEmpty(entry.Faction)) {
context.NPCFaction.TryAdd(entry.PilotNameLocalised, entry.Faction);
}
string? faction = context.LastRecordedAwardingFaction;
// We have seen a captain?
if (NPCs.IsWarzoneCaptain(entry.PilotName)) {
// if we have faction information, we can compare it to figure out
// whether it is the enemy or allied faction. but this is not always
// possible. In such a case we assume we have seen the enemy captain.
if (!string.IsNullOrEmpty(entry.Faction) &&
!string.IsNullOrEmpty(faction)) {
if (string.Compare(faction, entry.Faction) != 0) {
context.HaveSeenEnemyCaptain = true;
} else {
context.HaveSeenAlliedCaptain = true;
}
} else {
context.HaveSeenEnemyCaptain = true;
}
}
// Spec ops?
if (NPCs.IsSpecOps(entry.PilotName)) {
context.HaveSeenSpecOps = true;
}
// Correspondent?
if (NPCs.IsWarzoneCorrespondent(entry.PilotName)) {
// if we have faction information, we can compare it to figure out
// whether it is the enemy or allied faction. but this is not always
// possible. In such a case we assume we have seen the enemy
// correspondent.
if (!string.IsNullOrEmpty(entry.Faction) &&
!string.IsNullOrEmpty(faction)) {
if (string.Compare(faction, entry.Faction) != 0) {
context.HaveSeenEnemyCorrespondent = true;
} else {
context.HaveSeenAlliedCorrespondent = true;
}
} else {
context.HaveSeenEnemyCorrespondent = true;
}
}
}
}

View File

@@ -0,0 +1,14 @@
using EDPlayerJournal.Entries;
namespace EDPlayerJournal.BGS.Parsers;
internal class SupercruiseDestinationDropParser : ITransactionParserPart {
public void Parse(Entry entry, TransactionParserContext context, TransactionParserOptions options, TransactionList transactions) {
SupercruiseDestinationDropEntry? drop = entry as SupercruiseDestinationDropEntry;
if (drop == null || drop.Type == null) {
return;
}
context.CurrentInstanceType = drop.Type;
}
}

View File

@@ -1,664 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using EDPlayerJournal.Entries;
namespace EDPlayerJournal.BGS;
public class Report {
private List<Objective> objectives = new List<Objective>();
public delegate void OnLogDelegate(string log);
public event OnLogDelegate? OnLog;
public List<Objective> Objectives {
get { return objectives; }
set { objectives = value; }
}
public bool AddObjective(Objective objective) {
var found = objectives.Find(x => x.CompareTo(objective) == 0);
bool added = false;
if (found == null) {
objectives.Add(objective);
added = true;
}
return added;
}
public static bool IsRelevant(Entry e) {
return e.Is(Events.CommitCrime) ||
e.Is(Events.Docked) ||
e.Is(Events.FactionKillBond) ||
e.Is(Events.FSDJump) ||
e.Is(Events.Location) ||
e.Is(Events.MarketBuy) ||
e.Is(Events.MarketSell) ||
e.Is(Events.MissionAccepted) ||
e.Is(Events.MissionFailed) ||
e.Is(Events.MultiSellExplorationData) ||
e.Is(Events.RedeemVoucher) ||
e.Is(Events.SearchAndRescue) ||
e.Is(Events.SellExplorationData) ||
e.Is(Events.SellMicroResources) ||
e.Is(Events.SellOrganicData) ||
e.Is(Events.ShipTargeted) ||
e.Is(Events.MissionCompleted)
;
}
public void Scan(PlayerJournal journal, DateTime start, DateTime end) {
/* Log files only get rotated if you restart the game client. This means that there might
* be - say - entries from the 4th of May in the file with a timestamp of 3rd of May. This
* happens if you happen to play a session late into the night.
* At first I tried extracting the first and last line of a file to see the date range, but
* if you have a lot of files this becomes quite slow, and quite the memory hog (as journal
* files have to be read in their entirety to check this). So we assume that you can't play
* three days straight, and keep the code fast.
*/
DateTime actualstart = start.AddDays(-3);
List<Entry> entries = journal.Files
.Where(f => f.NormalisedDateTime >= actualstart && f.NormalisedDateTime <= end)
.SelectMany(e => e.Entries)
.ToList()
;
// Now further sort the list down to entries that are actually within the given datetime
// Note that entry datetimes are not normalised, so we have to sort until end + 1 day
DateTime actualend = end.AddDays(1);
entries = entries
.Where(e => e.Timestamp >= start && e.Timestamp < actualend)
.ToList()
;
Scan(entries);
}
public void Scan(List<Entry> entries) {
if (entries.Count <= 0) {
return;
}
List<Entry> relevant = entries
.Where(x => IsRelevant(x))
.ToList()
;
Dictionary<ulong, MissionAcceptedEntry> acceptedMissions = new Dictionary<ulong, MissionAcceptedEntry>();
Dictionary<string, long> buyCost = new Dictionary<string, long>();
Dictionary<ulong, string> systems = new Dictionary<ulong, string>();
Dictionary<string, string> npcfactions = new Dictionary<string, string>();
Dictionary<string, List<Faction>> system_factions = new Dictionary<string, List<Faction>>();
// A dictionary resolving to a station at which each mission was accepted
Dictionary<ulong, string> acceptedStations = new Dictionary<ulong, string>();
// A dictionary resolving to a system at which each mission was accepted
Dictionary<ulong, ulong> acceptedSystems = new Dictionary<ulong, ulong>();
string? current_system = null;
ulong? current_system_address = null;
string? current_station = null;
string? controlling_faction = null;
objectives.ForEach(x => x.Clear());
foreach (Entry e in relevant) {
List<Transaction> results = new List<Transaction>();
bool collate = false;
if (e.Is(Events.Docked)) {
DockedEntry? docked = e as DockedEntry;
if (docked == null) {
continue;
}
/* gleem the current station from this message
*/
current_station = docked.StationName;
current_system = docked.StarSystem;
controlling_faction = docked.StationFaction;
current_system_address = docked.SystemAddress;
if (current_system_address != null && current_system != null) {
if (!systems.ContainsKey(current_system_address.Value)) {
systems.Add(current_system_address.Value, current_system);
}
}
} else if (e.Is(Events.FSDJump)) {
/* Gleem current system and controlling faction from this message.
*/
FSDJumpEntry? fsd = e as FSDJumpEntry;
if (fsd == null || fsd.StarSystem == null) {
continue;
}
current_system_address = fsd.SystemAddress;
current_system = fsd.StarSystem;
controlling_faction = fsd.SystemFaction;
if (!systems.ContainsKey(fsd.SystemAddress)) {
systems.Add(fsd.SystemAddress, fsd.StarSystem);
}
if (!system_factions.ContainsKey(fsd.StarSystem) &&
fsd.SystemFactions.Count > 0) {
system_factions[fsd.StarSystem] = fsd.SystemFactions;
}
} else if (e.Is(Events.Location)) {
/* Get current system, faction name and station from Location message
*/
LocationEntry? location = e as LocationEntry;
if (location == null || location.StarSystem == null) {
continue;
}
current_system = location.StarSystem;
current_system_address = location.SystemAddress;
if (!systems.ContainsKey(location.SystemAddress)) {
systems.Add(location.SystemAddress, location.StarSystem);
}
if (!string.IsNullOrEmpty(location.SystemFaction)) {
controlling_faction = location.SystemFaction;
}
if (!string.IsNullOrEmpty(location.StationName)) {
current_station = location.StationName;
}
if (!system_factions.ContainsKey(location.StarSystem) &&
location.SystemFactions.Count > 0) {
system_factions[location.StarSystem] = location.SystemFactions;
}
} else if (e.Is(Events.ShipTargeted)) {
ShipTargetedEntry? targeted = e as ShipTargetedEntry;
if (targeted == null) {
continue;
}
if (string.IsNullOrEmpty(targeted.PilotNameLocalised) ||
string.IsNullOrEmpty(targeted.Faction)) {
continue;
}
npcfactions[targeted.PilotNameLocalised] = targeted.Faction;
} else if (e.Is(Events.CommitCrime)) {
CommitCrimeEntry? crime = e as CommitCrimeEntry;
if (crime == null) {
continue;
}
string? faction = crime.Faction;
if (faction == null || !crime.IsMurder) {
/* we don't care about anything but murder for now */
continue;
}
/* use localised victim name if we have it, otherwise use normal name */
string? victim = crime.VictimLocalised;
if (string.IsNullOrEmpty(victim)) {
victim = crime.Victim;
}
if (string.IsNullOrEmpty(victim)) {
continue;
}
if (!npcfactions.ContainsKey(victim)) {
/* The faction in the crime report is the faction that issues the bounty,
* and not the faction of the victim.
*/
OnLog?.Invoke(string.Format(
"No faction found for victim \"{0}\", using faction that issued the bounty instead.",
victim, crime.Faction
));
} else {
faction = npcfactions[victim];
}
results.Add(new FoulMurder(crime) {
System = current_system,
Faction = faction,
});
collate = true;
} else if (e.Is(Events.MissionCompleted)) {
MissionCompletedEntry? completed = e as MissionCompletedEntry;
if (completed == null ||
completed.Mission == null) {
continue;
}
MissionAcceptedEntry? accepted = null;
MissionCompleted? main_mission = null;
ulong accepted_address;
string? accepted_system;
string? target_faction_name = completed.Mission.TargetFaction;
string? source_faction_name = completed.Mission.Faction;
if (!acceptedMissions.TryGetValue(completed.Mission.MissionID, out accepted)) {
OnLog?.Invoke(string.Format(
"Unable to find mission acceptance for mission \"{0}\". " +
"Please extend range to include the mission acceptance.", completed.Mission.LocalisedName
));
continue;
}
if (!acceptedSystems.TryGetValue(completed.Mission.MissionID, out accepted_address)) {
OnLog?.Invoke(string.Format(
"Unable to figure out in which system mission \"{0}\" was accepted.", completed.Mission.LocalisedName
));
continue;
}
if (!systems.TryGetValue(accepted_address, out accepted_system)) {
OnLog?.Invoke(string.Format(
"Unable to figure out in which system mission \"{0}\" was accepted.", completed.Mission.LocalisedName
));
continue;
}
foreach (var other in completed.Mission.Influences) {
string faction = other.Key;
if (string.IsNullOrEmpty(faction)) {
OnLog?.Invoke(string.Format(
"Mission \"{0}\" has empty faction name in influence block, "+
"so this influence support was ignored. " +
"Please check the README on why this happens.", completed.Mission.LocalisedName)
);
continue;
}
/* Now comes the fun part. Sometimes the influence list is empty for a faction.
* This happens if the faction in question
*/
if (other.Value.Count() == 0) {
OnLog?.Invoke(string.Format(
"Mission \"{0}\" gave no influence to \"{1}\", so we assume this is because the " +
"faction is in a conflict and cannot gain influence right now. " +
"If this assessment is wrong, just remove the entry from the objective list.",
completed.Mission.LocalisedName, faction
));
if (string.Compare(target_faction_name, faction, true) == 0) {
/* here we assume that if the faction in question is the target faction,
* that we gave said target faction no influence in the target system, aka
* current system
*/
if (current_system_address == null) {
continue;
}
other.Value.Add(current_system_address.Value, "");
OnLog?.Invoke(string.Format(
"Mission \"{0}\" gave no influence to \"{1}\". Since \"{1}\" is the target faction " +
"of the mission, we assume the influence was gained in \"{2}\". " +
"Please remove the entry if this assumption is wrong.",
completed.Mission.LocalisedName, faction, current_system
));
} else if (string.Compare(source_faction_name, faction, true) == 0) {
/* source faction of the mission is not getting any influence. This could be because
* the source faction is in an election state in its home system and cannot gain any
* influence. It may also very well be that the source and target faction are the same
* since the faction is present in both target and source system. In which case we add
* both and hope for the best.
*/
other.Value.Add(accepted_address, "");
OnLog?.Invoke(string.Format(
"Mission \"{0}\" gave no influence to \"{1}\". Since \"{1}\" is the source faction " +
"of the mission, we assume the influence was gained in \"{2}\". " +
"Please remove the entry if this assumption is wrong.",
completed.Mission.LocalisedName, faction, accepted_system
));
/* check if source/target faction are equal, in which case we also need an entry
* for the target system. As said factions can be present in two systems, and can
* give missions that target each other.
*/
if (string.Compare(source_faction_name, target_faction_name, true) == 0 &&
current_system_address != null) {
other.Value.Add(current_system_address.Value, "");
OnLog?.Invoke(string.Format(
"Mission \"{0}\" gave no influence to \"{1}\". Since \"{1}\" is the source and target faction " +
"of the mission, we assume the influence was also gained in target system \"{2}\". " +
"Please remove the entry if this assumption is wrong.",
completed.Mission.LocalisedName, faction, current_system
));
}
}
}
foreach (var influences in other.Value) {
ulong system_address = influences.Key;
string? system;
string? accepted_station;
if (completed.Mission == null) {
continue;
}
if (!systems.TryGetValue(system_address, out system)) {
OnLog?.Invoke(string.Format(
"Unknown system \"{0}\" unable to assign that mission a target.", system_address
));
continue;
}
if (!acceptedStations.TryGetValue(completed.Mission.MissionID, out accepted_station)) {
OnLog?.Invoke(string.Format(
"Unable to figure out in which station mission \"{0}\" was accepted.", completed.Mission.LocalisedName
));
continue;
}
if (faction.Equals(source_faction_name) && system_address == accepted_address) {
/* This is the influence block for the origin of the mission.
*/
main_mission = new MissionCompleted(completed) {
System = accepted_system,
Faction = source_faction_name,
SystemAddress = accepted_address,
Station = accepted_station,
};
results.Add(main_mission);
} else if (!faction.Equals(source_faction_name) ||
(faction.Equals(source_faction_name) && system_address != accepted_address)) {
/* This block is for secondary factions (first if), or if the secondary faction
* is the same as the mission giver, but in another system (second if).
*/
results.Add(new InfluenceSupport() {
Faction = faction,
Influence = influences.Value,
System = system,
SystemAddress = system_address,
RelevantMission = completed
});
}
}
}
} else if (e.Is(Events.MissionAccepted)) {
MissionAcceptedEntry? accepted = e as MissionAcceptedEntry;
if (accepted == null ||
current_station == null ||
current_system_address == null) {
continue;
}
if (accepted.Mission == null) {
continue;
}
ulong id = accepted.Mission.MissionID;
if (!acceptedMissions.ContainsKey(id)) {
acceptedMissions[id] = accepted;
}
if (!acceptedStations.ContainsKey(id)) {
acceptedStations[id] = current_station;
}
if (!acceptedSystems.ContainsKey(id)) {
acceptedSystems[id] = current_system_address.Value;
}
} else if (e.Is(Events.MissionFailed)) {
MissionFailedEntry? failed = e as MissionFailedEntry;
MissionAcceptedEntry? accepted = null;
ulong accepted_address = 0;
string? accepted_system;
string? accepted_station;
if (failed == null || failed.Mission == null) {
continue;
}
if (!acceptedMissions.TryGetValue(failed.Mission.MissionID, out accepted)) {
OnLog?.Invoke("A mission failed which wasn't accepted in the given time frame. " +
"Please adjust start date to when the mission was accepted to include it in the list.");
continue;
}
if (accepted.Mission == null) {
continue;
}
if (!acceptedSystems.TryGetValue(failed.Mission.MissionID, out accepted_address)) {
OnLog?.Invoke(string.Format(
"Unable to figure out in which system mission \"{0}\" was accepted.", accepted.Mission.Name
));
continue;
}
if (!systems.TryGetValue(accepted_address, out accepted_system)) {
OnLog?.Invoke(string.Format(
"Unable to figure out in which system mission \"{0}\" was accepted.", accepted.Mission.Name
));
continue;
}
if (!acceptedStations.TryGetValue(failed.Mission.MissionID, out accepted_station)) {
OnLog?.Invoke(string.Format(
"Unable to figure out in which station mission \"{0}\" was accepted.", accepted.Mission.Name
));
continue;
}
results.Add(new MissionFailed(accepted) {
Failed = failed,
System = accepted_system,
Station = accepted_station,
Faction = accepted.Mission.Faction,
SystemAddress = accepted_address,
});
/* Mission failed should be collated if they are in the same system/station
*/
collate = true;
} else if (e.Is(Events.SellExplorationData)) {
SellExplorationDataEntry? explo = e as SellExplorationDataEntry;
if (explo == null) {
continue;
}
results.Add(new Cartographics(explo) {
System = current_system,
Station = current_station,
Faction = controlling_faction,
});
/* colate single cartographic selling into one */
collate = true;
} else if (e.Is(Events.SellOrganicData)) {
SellOrganicDataEntry? organic = e as SellOrganicDataEntry;
if (organic == null) {
return;
}
/* organic data sold to Vista Genomics */
results.Add(new OrganicData(organic) {
System = current_system,
Station = current_station,
Faction = controlling_faction,
});
collate = true;
} else if (e.Is(Events.MultiSellExplorationData)) {
MultiSellExplorationDataEntry? explo = e as MultiSellExplorationDataEntry;
if (explo == null) {
continue;
}
/* For multi-sell-exploraton-data only the controlling faction of the station sold to matters.
*/
results.Add(new Cartographics(explo) {
System = current_system,
Station = current_station,
Faction = controlling_faction
});
collate = true;
} else if (e.Is(Events.RedeemVoucher)) {
RedeemVoucherEntry? voucher = e as RedeemVoucherEntry;
List<Faction> current_factions = new List<Faction>();
if (voucher == null || current_system == null) {
continue;
}
if (system_factions.ContainsKey(current_system)) {
current_factions = system_factions[current_system];
} else {
OnLog?.Invoke("There are no current system factions, so turned in vouchers were ignored.");
continue;
}
foreach (string faction in voucher.Factions) {
if (current_factions.Find(x => x.Name == faction) == null) {
OnLog?.Invoke(
string.Format("Vouchers for \"{0}\" were ignored in \"{1}\" since said " +
"faction is not present there.", faction, current_system)
);
continue; /* faction is not present, so it is ignored */
}
/* Same for selling combat vouchers. Only the current controlling faction matters here.
*/
results.Add(new Vouchers(voucher) {
System = current_system,
Station = current_station,
Faction = faction,
ControllingFaction = controlling_faction,
});
}
collate = true;
} else if (e.Is(Events.SellMicroResources)) {
SellMicroResourcesEntry? smr = e as SellMicroResourcesEntry;
if (smr == null) {
continue;
}
results.Add(new SellMicroResources(smr) {
Faction = controlling_faction,
Station = current_station,
System = current_system
});
} else if (e.Is(Events.MarketBuy)) {
MarketBuyEntry? buy = e as MarketBuyEntry;
if (buy == null) {
continue;
}
if (string.IsNullOrEmpty(buy.Type) || buy.BuyPrice == 0) {
continue;
}
buyCost[buy.Type] = buy.BuyPrice;
results.Add(new BuyCargo(buy) {
Faction = controlling_faction,
Station = current_station,
System = current_system,
});
collate = true;
} else if (e.Is(Events.SearchAndRescue)) {
SearchAndRescueEntry? sar = e as SearchAndRescueEntry;
if (sar == null) {
continue;
}
results.Add(new SearchAndRescue(sar) {
Faction = controlling_faction,
Station = current_station,
System = current_system,
});
collate = true;
} else if (e.Is(Events.MarketSell)) {
MarketSellEntry? sell = e as MarketSellEntry;
long profit = 0;
if (sell == null || sell.Type == null) {
continue;
}
if (!buyCost.ContainsKey(sell.Type)) {
OnLog?.Invoke("Could not find buy order for the given commodity. Please adjust profit manually.");
} else {
long avg = buyCost[sell.Type];
profit = (long)sell.TotalSale - (avg * sell.Count);
}
results.Add(new SellCargo(sell) {
Faction = controlling_faction,
Station = current_station,
System = current_system,
Profit = profit
});
}
if (results == null || results.Count <= 0) {
continue;
}
foreach (Transaction entry in results) {
/* Find all objectives that generally match.
*/
var matches = objectives
.Where(x => x.Matches(entry) >= 2)
.OrderBy(x => x.Matches(entry))
;
Objective? objective = null;
if (matches != null && matches.Count() > 0) {
/* Then select the one that matches the most.
*/
objective = matches
.OrderBy(x => x.Matches(entry))
.Reverse()
.First()
;
} else {
/* create a new objective if we don't have one */
objective = new Objective() {
Station = (entry.Station ?? ""),
Faction = (entry.Faction ?? ""),
System = (entry.System ?? ""),
};
objectives.Add(objective);
}
Transaction? existing = null;
existing = objective.LogEntries.Find(x => {
try {
return x.CompareTo(entry) == 0;
} catch (NotImplementedException) {
return false;
}
});
if (collate && existing != null) {
existing.Entries.Add(e);
} else if (!collate || existing == null) {
objective.LogEntries.Add(entry);
}
}
}
}
public void Scan(PlayerJournal journal) {
Scan(journal, DateTime.Now, DateTime.Now);
}
}

View File

@@ -1,9 +1,4 @@
using EDPlayerJournal.Entries; using EDPlayerJournal.Entries;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace EDPlayerJournal.BGS; namespace EDPlayerJournal.BGS;
@@ -25,10 +20,18 @@ public class ThargoidKill : Transaction {
get { return (ulong)Entries.OfType<FactionKillBondEntry>().Sum(x => (decimal)x.Reward); } get { return (ulong)Entries.OfType<FactionKillBondEntry>().Sum(x => (decimal)x.Reward); }
} }
/// <summary>
/// Kiling thargoids contributes to the system wide effort of
/// the thargoid war.
/// </summary>
public override bool SystemContribution {
get { return true; }
}
public ThargoidKill() { } public ThargoidKill() { }
public ThargoidKill(FactionKillBondEntry entry) { public ThargoidKill(FactionKillBondEntry entry) {
if (string.Compare(entry.VictimFaction, Thargoid.ThargoidFaction) != 0) { if (!Factions.IsThargoid(entry.VictimFaction)) {
throw new Exception("Not a valid thargoid kill"); throw new Exception("Not a valid thargoid kill");
} }

View File

@@ -1,25 +1,52 @@
using EDPlayerJournal.Entries; using EDPlayerJournal.Entries;
using System.ComponentModel.DataAnnotations.Schema;
namespace EDPlayerJournal.BGS; namespace EDPlayerJournal.BGS;
public class Transaction : IComparable<Transaction> { public class Transaction : IComparable<Transaction> {
public List<Entry> Entries { get; } = new List<Entry>(); public List<Entry> Entries { get; } = new List<Entry>();
public virtual string CompletedAt { public string CompletedAt {
get {
DateTime? datetime = CompletedAtDateTime;
if (datetime == null) {
return "Unknown";
}
return datetime.Value.ToString("dd.MM.yyyy HH:mm UTC");
}
}
public virtual DateTime? CompletedAtDateTime {
get { get {
var items = Entries var items = Entries
.OrderBy(x => x.Timestamp) .OrderBy(x => x.Timestamp)
.ToArray() .ToArray()
; ;
if (items == null || items.Length == 0) { if (items == null || items.Length == 0) {
return "Unknown"; return null;
} }
Entry last = items.Last(); Entry last = items.Last();
return last.Timestamp.ToString("dd.MM.yyyy HH:mm UTC"); return last.Timestamp;
} }
} }
/// <summary>
/// Whether the transaction helps the entire system.
/// While all transactions are related to one faction, sometimes their
/// overall effect on the system as a whole is more important, than to
/// their faction. For example, rescuing refugees helps the Thargoid war
/// effort in the system as a whole, not just for the faction.
/// So this is true for transactions that help the system as a whole.
/// </summary>
public virtual bool SystemContribution { get; } = false;
/// <summary>
/// Returns true if this transaction was completed in legacy ED
/// </summary>
public bool IsLegacy { get; set; } = false;
/// <summary> /// <summary>
/// Controlling faction of the station this entry was made/turned into. /// Controlling faction of the station this entry was made/turned into.
/// </summary> /// </summary>

View File

@@ -1,103 +1,48 @@
using EDPlayerJournal.Entries; using EDPlayerJournal.BGS.Parsers;
using System.Collections.Generic; using EDPlayerJournal.Entries;
using System.Reflection.Metadata.Ecma335;
namespace EDPlayerJournal.BGS; namespace EDPlayerJournal.BGS;
internal class TransactionParserContext { public class TransactionParserOptions {
public string? CurrentSystem { get; set; } /// <summary>
public ulong? CurrentSystemAddress { get; set; } /// Whether to ignore exo biology. It does not contribute to BGS, so this
public string? CurrentStation { get; set; } /// is true per default.
public string? ControllingFaction { get; set; } /// </summary>
public bool IgnoreExoBiology { get; set; } = true;
/// <summary> /// <summary>
/// A list of accepted missions index by their mission ID /// Whether to ignore influence support. Usually one only cares about the
/// primary faction for the influence.
/// </summary> /// </summary>
public Dictionary<ulong, MissionAcceptedEntry> AcceptedMissions { get; } = new(); public bool IgnoreInfluenceSupport { get; set; } = false;
public Dictionary<ulong, Location> AcceptedMissionLocation { get; } = new();
/// <summary> /// <summary>
/// A way to lookup a system by its system id /// Whether to ignore market buy. Buying from a market gives a small amount
/// of INF if it is sold to a high demand market, but generally one buys from
/// a market to aid the faction the stuff is being sold to. So allow it to be
/// disabled.
/// </summary> /// </summary>
public Dictionary<ulong, string> SystemsByID { get; } = new(); public bool IgnoreMarketBuy { get; set; } = false;
/// <summary> /// <summary>
/// A list of factions present in the given star system /// Whether we should ignore things done for the fleet carrier faction.
/// </summary> /// </summary>
public Dictionary<string, List<Faction>> SystemFactions { get; } = new(); public bool IgnoreFleetCarrierFaction { get; set; } = true;
/// <summary>
/// To which faction a given named NPC belonged to.
/// </summary>
public Dictionary<string, string> NPCFaction { get; } = new();
/// <summary>
/// Buy costs for a given commodity
/// </summary>
public Dictionary<string, long> BuyCost = new();
public void BoughtCargo(string? cargo, long? cost) {
if (cargo == null || cost == null) {
return;
}
BuyCost[cargo] = cost.Value;
}
public List<Faction>? GetFactions(string? system) {
if (system == null || !SystemFactions.ContainsKey(system)) {
return null;
}
return SystemFactions[system];
}
public void MissionAccepted(MissionAcceptedEntry accepted) {
if (CurrentSystem == null || CurrentSystemAddress == null) {
throw new Exception("Mission accepted without knowing where.");
}
if (accepted.Mission == null) {
throw new Exception("Mission is null");
}
AcceptedMissions.TryAdd(accepted.Mission.MissionID, accepted);
Location location = new() {
StarSystem = CurrentSystem,
SystemAddress = CurrentSystemAddress.Value,
Station = (CurrentStation ?? ""),
};
AcceptedMissionLocation.TryAdd(accepted.Mission.MissionID, location);
}
} }
public class TransactionList : List<Transaction> { public class TransactionList : List<Transaction> {
public void AddIncomplete(Transaction underlying, string reason) { public void AddIncomplete(Transaction underlying, string reason, Entry entry) {
Add(new IncompleteTransaction(underlying, reason)); Add(new IncompleteTransaction(underlying, reason, entry));
} }
} }
internal interface TransactionParserPart{
/// <summary>
/// Parse a given entry of the entry type specified when declaring to implement this
/// interface. You must add your transaction to the passed list in case you did have
/// enough information to parse one or more. You may update the parser context
/// with new information in case the entry yielded new information.
/// Throw an exception if there was an error or a malformed entry. If you have an
/// incomplete entry, i.e. not enough information to complete one, add an
/// IncompleteTransaction to the list
/// </summary>
/// <param name="entry">The entry to parse</param>
/// <param name="context">Parsing context that may contain useful information</param>
/// <param name="transactions">List of parsed transactions</param>
public void Parse(Entry entry, TransactionParserContext context, TransactionList transactions);
}
/// <summary> /// <summary>
/// The location parser only updates the context with useful information, and does not /// The location parser only updates the context with useful information, and does not
/// by itself generate any transactions. Location is the best information gatherer here /// by itself generate any transactions. Location is the best information gatherer here
/// as we are getting controlling faction, system factions, address and station name. /// as we are getting controlling faction, system factions, address and station name.
/// </summary> /// </summary>
internal class LocationParser : TransactionParserPart { internal class LocationParser : ITransactionParserPart {
public void Parse(Entry e, TransactionParserContext context, TransactionList transactions) { public void Parse(Entry e, TransactionParserContext context, TransactionParserOptions options, TransactionList transactions) {
LocationEntry? entry = e as LocationEntry; LocationEntry? entry = e as LocationEntry;
if (entry == null) { if (entry == null) {
throw new NotImplementedException(); throw new NotImplementedException();
@@ -120,15 +65,20 @@ internal class LocationParser : TransactionParserPart {
context.CurrentStation = entry.StationName; context.CurrentStation = entry.StationName;
} }
if (!context.SystemFactions.ContainsKey(entry.StarSystem) && if (!string.IsNullOrEmpty(entry.StationFaction)) {
entry.SystemFactions != null && entry.SystemFactions.Count > 0) { context.StationOwner = entry.StationFaction;
} else {
context.StationOwner = null;
}
if (entry.SystemFactions != null && entry.SystemFactions.Count > 0) {
context.SystemFactions[entry.StarSystem] = entry.SystemFactions; context.SystemFactions[entry.StarSystem] = entry.SystemFactions;
} }
} }
} }
internal class FSDJumpParser : TransactionParserPart { internal class FSDJumpParser : ITransactionParserPart {
public void Parse(Entry e, TransactionParserContext context, TransactionList transactions) { public void Parse(Entry e, TransactionParserContext context, TransactionParserOptions options, TransactionList transactions) {
FSDJumpEntry? entry = e as FSDJumpEntry; FSDJumpEntry? entry = e as FSDJumpEntry;
if (entry == null) { if (entry == null) {
throw new NotImplementedException(); throw new NotImplementedException();
@@ -138,6 +88,14 @@ internal class FSDJumpParser : TransactionParserPart {
throw new InvalidJournalEntryException(); throw new InvalidJournalEntryException();
} }
// If you FSD jump straight out of the combat zone into a different system
// then the combat zone will be placed in the wrong system otherwise.
// This call needs to be *before* changing the current system.
context.DiscernCombatZone(transactions, e);
context.ResetCombatZone();
context.LeftInstance();
context.CurrentSystem = entry.StarSystem; context.CurrentSystem = entry.StarSystem;
context.CurrentSystemAddress = entry.SystemAddress; context.CurrentSystemAddress = entry.SystemAddress;
@@ -147,15 +105,14 @@ internal class FSDJumpParser : TransactionParserPart {
context.ControllingFaction = entry.SystemFaction; context.ControllingFaction = entry.SystemFaction;
} }
if (!context.SystemFactions.ContainsKey(entry.StarSystem) && if (entry.SystemFactions != null && entry.SystemFactions.Count > 0) {
entry.SystemFactions != null && entry.SystemFactions.Count > 0) {
context.SystemFactions[entry.StarSystem] = entry.SystemFactions; context.SystemFactions[entry.StarSystem] = entry.SystemFactions;
} }
} }
} }
internal class DockedParser : TransactionParserPart { internal class DockedParser : ITransactionParserPart {
public void Parse(Entry e, TransactionParserContext context, TransactionList transactions) { public void Parse(Entry e, TransactionParserContext context, TransactionParserOptions options, TransactionList transactions) {
DockedEntry? entry = e as DockedEntry; DockedEntry? entry = e as DockedEntry;
if (entry == null) { if (entry == null) {
throw new NotImplementedException(); throw new NotImplementedException();
@@ -171,7 +128,7 @@ internal class DockedParser : TransactionParserPart {
context.SystemsByID.TryAdd(entry.SystemAddress.Value, entry.StarSystem); context.SystemsByID.TryAdd(entry.SystemAddress.Value, entry.StarSystem);
if (!string.IsNullOrEmpty(entry.StationFaction)) { if (!string.IsNullOrEmpty(entry.StationFaction)) {
context.ControllingFaction = entry.StationFaction; context.StationOwner = entry.StationFaction;
} }
if (!string.IsNullOrEmpty(entry.StationName)) { if (!string.IsNullOrEmpty(entry.StationName)) {
@@ -180,32 +137,12 @@ internal class DockedParser : TransactionParserPart {
} }
} }
/// <summary>
/// With ship targeted we might find out to which faction a given NPC belonged. This is
/// useful later when said NPC gets killed or murdered.
/// </summary>
internal class ShipTargetedParser : TransactionParserPart {
public void Parse(Entry e, TransactionParserContext context, TransactionList transactions) {
ShipTargetedEntry? entry = e as ShipTargetedEntry;
if (entry == null) {
throw new NotImplementedException();
}
// Scan happens in stages, and sometimes this information is not known
// yet. Do now throw an error, this is expected behaviour.
if (!string.IsNullOrEmpty(entry.PilotNameLocalised) &&
!string.IsNullOrEmpty(entry.Faction)) {
context.NPCFaction.TryAdd(entry.PilotNameLocalised, entry.Faction);
}
}
}
/// <summary> /// <summary>
/// Commit crime can result in a transaction, especially if the crime committed is /// Commit crime can result in a transaction, especially if the crime committed is
/// murder. /// murder.
/// </summary> /// </summary>
internal class CommitCrimeParser : TransactionParserPart { internal class CommitCrimeParser : ITransactionParserPart {
public void Parse(Entry e, TransactionParserContext context, TransactionList transactions) { public void Parse(Entry e, TransactionParserContext context, TransactionParserOptions options, TransactionList transactions) {
CommitCrimeEntry? entry = e as CommitCrimeEntry; CommitCrimeEntry? entry = e as CommitCrimeEntry;
if (entry == null) { if (entry == null) {
throw new NotImplementedException(); throw new NotImplementedException();
@@ -228,25 +165,69 @@ internal class CommitCrimeParser : TransactionParserPart {
victim = "Unknown"; victim = "Unknown";
} }
if (!context.NPCFaction.ContainsKey(victim)) { string faction;
if (entry.IsCrime(CrimeTypes.OnFootMurder)) {
if (entry.Faction == null) {
transactions.AddIncomplete( transactions.AddIncomplete(
new FoulMurder(), new FoulMurder(),
"Crime victim was not properly scanned." "On foot murder victim did not have a faction", e
); );
return; return;
} }
// On foot murders are different, there the faction is
string faction = context.NPCFaction[victim]; // the faction the NPC belonged too
faction = entry.Faction;
} else {
if (!context.NPCFaction.ContainsKey(victim)) {
transactions.AddIncomplete(
new FoulMurder(),
"Crime victim was not properly scanned.", e
);
return;
}
faction = context.NPCFaction[victim];
}
transactions.Add(new FoulMurder(entry) { transactions.Add(new FoulMurder(entry) {
System = context.CurrentSystem, System = context.CurrentSystem,
IsLegacy = context.IsLegacy,
Faction = faction, Faction = faction,
}); });
} }
} }
internal class MissionAcceptedParser : TransactionParserPart { internal class MissionsParser : ITransactionParserPart {
public void Parse(Entry e, TransactionParserContext context, TransactionList transactions) { public void Parse(Entry entry, TransactionParserContext context, TransactionParserOptions options, TransactionList transactions) {
MissionsEntry? missions = entry as MissionsEntry;
if (missions == null) {
return;
}
if (context.CurrentSystem == null || context.CurrentSystemAddress == null) {
transactions.AddIncomplete(new MissionCompleted(),
"Could not determine current location on Missions event.",
entry
);
return;
}
foreach (Mission mission in missions.Active) {
try {
context.MissionAccepted(mission);
} catch (Exception exception) {
transactions.AddIncomplete(new MissionCompleted(),
exception.Message,
entry
);
}
}
}
}
internal class MissionAcceptedParser : ITransactionParserPart {
public void Parse(Entry e, TransactionParserContext context, TransactionParserOptions options, TransactionList transactions) {
MissionAcceptedEntry? entry = e as MissionAcceptedEntry; MissionAcceptedEntry? entry = e as MissionAcceptedEntry;
if (entry == null) { if (entry == null) {
throw new NotImplementedException(); throw new NotImplementedException();
@@ -254,7 +235,8 @@ internal class MissionAcceptedParser : TransactionParserPart {
if (context.CurrentSystem == null || context.CurrentSystemAddress == null) { if (context.CurrentSystem == null || context.CurrentSystemAddress == null) {
transactions.AddIncomplete(new MissionCompleted(), transactions.AddIncomplete(new MissionCompleted(),
"Could not determine current location on mission acceptance." "Could not determine current location on mission acceptance.",
e
); );
return; return;
} }
@@ -263,36 +245,37 @@ internal class MissionAcceptedParser : TransactionParserPart {
context.MissionAccepted(entry); context.MissionAccepted(entry);
} catch (Exception exception) { } catch (Exception exception) {
transactions.AddIncomplete(new MissionCompleted(), transactions.AddIncomplete(new MissionCompleted(),
exception.Message exception.Message,
e
); );
} }
} }
} }
internal class MissionCompletedParser : TransactionParserPart { internal class MissionCompletedParser : ITransactionParserPart {
public void Parse(Entry e, TransactionParserContext context, TransactionList transactions) { public void Parse(Entry e, TransactionParserContext context, TransactionParserOptions options, TransactionList transactions) {
MissionCompletedEntry? entry = e as MissionCompletedEntry; MissionCompletedEntry? entry = e as MissionCompletedEntry;
if (entry == null || entry.Mission == null) { if (entry == null || entry.Mission == null) {
throw new NotImplementedException(); throw new NotImplementedException();
} }
MissionAcceptedEntry? accepted = null; Mission? mission;
Location? accepted_location = null; Location? accepted_location;
string? target_faction_name = entry.Mission.TargetFaction; string? target_faction_name = entry.Mission.TargetFaction;
string? source_faction_name = entry.Mission.Faction; string? source_faction_name = entry.Mission.Faction;
// We did not find when the mission was accepted. // We did not find when the mission was accepted.
if (!context.AcceptedMissions.TryGetValue(entry.Mission.MissionID, out accepted)) { if (!context.AcceptedMissions.TryGetValue(entry.Mission.MissionID, out mission)) {
transactions.AddIncomplete(new MissionCompleted(), transactions.AddIncomplete(new MissionCompleted(),
String.Format("Mission acceptance for mission id {0} was not found", String.Format("Mission acceptance for mission id {0} was not found",
entry.Mission.MissionID)); entry.Mission.MissionID), e);
return; return;
} }
if (!context.AcceptedMissionLocation.TryGetValue(entry.Mission.MissionID, out accepted_location)) { if (!context.AcceptedMissionLocation.TryGetValue(entry.Mission.MissionID, out accepted_location)) {
transactions.AddIncomplete(new MissionCompleted(), transactions.AddIncomplete(new MissionCompleted(),
String.Format("Location for acceptance for mission id {0} was not found", String.Format("Location for acceptance for mission id {0} was not found",
entry.Mission.MissionID)); entry.Mission.MissionID), e);
return; return;
} }
@@ -315,20 +298,20 @@ internal class MissionCompletedParser : TransactionParserPart {
if (context.CurrentSystemAddress == null) { if (context.CurrentSystemAddress == null) {
continue; continue;
} }
other.Value.Add(context.CurrentSystemAddress.Value, ""); other.Value.Add(context.CurrentSystemAddress.Value, new MissionInfluence());
// Mission gave no influence to the target faction, so we assume // Mission gave no influence to the target faction, so we assume
// the target faction was in the same system. // the target faction was in the same system.
} else if (string.Compare(source_faction_name, faction, true) == 0) { } else if (string.Compare(source_faction_name, faction, true) == 0) {
// This happens if the source faction is not getting any influence // This happens if the source faction is not getting any influence
// This could be if the source faction is in a conflict, and thus does // This could be if the source faction is in a conflict, and thus does
// not gain any influence at all. // not gain any influence at all.
other.Value.Add(accepted_location.SystemAddress, ""); other.Value.Add(accepted_location.SystemAddress, new MissionInfluence());
// Just check if the target/source faction are the same, in which case // Just check if the target/source faction are the same, in which case
// we also have to make an additional entry // we also have to make an additional entry
if (string.Compare(source_faction_name, target_faction_name, true) == 0 && if (string.Compare(source_faction_name, target_faction_name, true) == 0 &&
context.CurrentSystemAddress != null) { context.CurrentSystemAddress != null) {
other.Value.Add(context.CurrentSystemAddress.Value, ""); other.Value.Add(context.CurrentSystemAddress.Value, new MissionInfluence());
} }
} }
} }
@@ -340,7 +323,8 @@ internal class MissionCompletedParser : TransactionParserPart {
if (!context.SystemsByID.TryGetValue(system_address, out system)) { if (!context.SystemsByID.TryGetValue(system_address, out system)) {
transactions.AddIncomplete(new MissionCompleted(), transactions.AddIncomplete(new MissionCompleted(),
string.Format("Unknown system {0}, unable to assign that mission a target", system_address) string.Format("Unknown system {0}, unable to assign that mission a target", system_address),
e
); );
continue; continue;
} }
@@ -349,24 +333,33 @@ internal class MissionCompletedParser : TransactionParserPart {
system_address == accepted_location.SystemAddress) { system_address == accepted_location.SystemAddress) {
// Source and target faction are the same, and this is the block // Source and target faction are the same, and this is the block
// for the source system. So we make a full mission completed entry. // for the source system. So we make a full mission completed entry.
transactions.Add(new MissionCompleted(entry) { transactions.Add(new MissionCompleted() {
CompletedEntry = entry,
Mission = mission,
System = accepted_location.StarSystem, System = accepted_location.StarSystem,
Faction = source_faction_name, Faction = source_faction_name,
SystemAddress = accepted_location.SystemAddress, SystemAddress = accepted_location.SystemAddress,
Station = accepted_location.Station, Station = accepted_location.Station,
IsLegacy = context.IsLegacy,
}); });
} else if (string.Compare(faction, source_faction_name, true) != 0 || } else if (string.Compare(faction, source_faction_name, true) != 0 ||
(string.Compare(faction, source_faction_name) == 0 && (string.Compare(faction, source_faction_name) == 0 &&
system_address != accepted_location.SystemAddress)) { system_address != accepted_location.SystemAddress)) {
// Whether we ignore influence support
if (options.IgnoreInfluenceSupport) {
continue;
}
// Source or target faction are different, and/or the system // Source or target faction are different, and/or the system
// differs. Sometimes missions go to different systems but to // differs. Sometimes missions go to different systems but to
// the same faction. // the same faction.
transactions.Add(new InfluenceSupport() { transactions.Add(new InfluenceSupport() {
Mission = mission,
Faction = faction, Faction = faction,
Influence = influences.Value, Influence = influences.Value,
System = system, System = system,
SystemAddress = system_address, SystemAddress = system_address,
RelevantMission = entry, RelevantMission = entry,
IsLegacy = context.IsLegacy,
}); });
} }
} }
@@ -374,11 +367,11 @@ internal class MissionCompletedParser : TransactionParserPart {
} }
} }
internal class MissionFailedParser : TransactionParserPart { internal class MissionFailedParser : ITransactionParserPart {
public void Parse(Entry e, TransactionParserContext context, TransactionList transactions) { public void Parse(Entry e, TransactionParserContext context, TransactionParserOptions options, TransactionList transactions) {
MissionAcceptedEntry? accepted = null; Mission? mission;
Location? accepted_location = null; Location? accepted_location;
string? accepted_system = null; string? accepted_system;
MissionFailedEntry? entry = e as MissionFailedEntry; MissionFailedEntry? entry = e as MissionFailedEntry;
if (entry == null) { if (entry == null) {
@@ -389,85 +382,48 @@ internal class MissionFailedParser : TransactionParserPart {
throw new InvalidJournalEntryException("No mission specified in mission failure"); throw new InvalidJournalEntryException("No mission specified in mission failure");
} }
if (!context.AcceptedMissions.TryGetValue(entry.Mission.MissionID, out accepted)) { if (!context.AcceptedMissions.TryGetValue(entry.Mission.MissionID, out mission)) {
transactions.AddIncomplete(new MissionFailed(), transactions.AddIncomplete(new MissionFailed(),
"Mission acceptance was not found" "Mission acceptance was not found", e
); );
return; return;
} }
if (!context.AcceptedMissionLocation.TryGetValue(entry.Mission.MissionID, out accepted_location)) { if (!context.AcceptedMissionLocation.TryGetValue(mission.MissionID, out accepted_location)) {
transactions.AddIncomplete(new MissionFailed(), transactions.AddIncomplete(new MissionFailed(),
"Unable to figure out where failed mission was accepted" "Unable to figure out where failed mission was accepted", e
); );
return; return;
} }
if (!context.SystemsByID.TryGetValue(accepted_location.SystemAddress, out accepted_system)) { if (!context.SystemsByID.TryGetValue(accepted_location.SystemAddress, out accepted_system)) {
transactions.AddIncomplete(new MissionFailed(), transactions.AddIncomplete(new MissionFailed(),
"Unable to figure out in which system failed mission was accepted" "Unable to figure out in which system failed mission was accepted", e
); );
return; return;
} }
transactions.Add(new MissionFailed() { if (string.IsNullOrEmpty(mission.Faction)) {
Accepted = accepted, transactions.AddIncomplete(new MissionFailed(),
Faction = accepted.Mission?.Faction, "Could not determine for what faction you failed a mission. This happens if the " +
Failed = entry, "mission acceptance is not within the given time frame.",
entry
);
}
transactions.Add(new MissionFailed(entry) {
Faction = mission?.Faction,
Mission = mission,
Station = accepted_location.Station, Station = accepted_location.Station,
System = accepted_location.StarSystem, System = accepted_location.StarSystem,
SystemAddress = accepted_location.SystemAddress, SystemAddress = accepted_location.SystemAddress,
IsLegacy = context.IsLegacy,
}); });
} }
} }
internal class SellExplorationDataParser : TransactionParserPart { internal class RedeemVoucherParser : ITransactionParserPart {
public void Parse(Entry e, TransactionParserContext context, TransactionList transactions) { public void Parse(Entry e, TransactionParserContext context, TransactionParserOptions options, TransactionList transactions) {
SellExplorationDataEntry? entry = e as SellExplorationDataEntry;
if (entry == null) {
throw new NotImplementedException();
}
transactions.Add(new Cartographics(entry) {
System = context.CurrentSystem,
Station = context.CurrentStation,
Faction = context.ControllingFaction,
});
}
}
internal class SellOrganicDataParser : TransactionParserPart {
public void Parse(Entry e, TransactionParserContext context, TransactionList transactions) {
SellOrganicDataEntry? entry = e as SellOrganicDataEntry;
if (entry == null) {
throw new NotImplementedException();
}
transactions.Add(new OrganicData(entry) {
System = context.CurrentSystem,
Station = context.CurrentStation,
Faction = context.ControllingFaction,
});
}
}
internal class MultiSellExplorationDataParser : TransactionParserPart {
public void Parse(Entry e, TransactionParserContext context, TransactionList transactions) {
MultiSellExplorationDataEntry? entry = e as MultiSellExplorationDataEntry;
if (entry == null) {
throw new NotImplementedException();
}
transactions.Add(new Cartographics(entry) {
System = context.CurrentSystem,
Station = context.CurrentStation,
Faction = context.ControllingFaction,
});
}
}
internal class RedeemVoucherParser : TransactionParserPart {
public void Parse(Entry e, TransactionParserContext context, TransactionList transactions) {
RedeemVoucherEntry? entry = e as RedeemVoucherEntry; RedeemVoucherEntry? entry = e as RedeemVoucherEntry;
if (entry == null) { if (entry == null) {
throw new NotImplementedException(); throw new NotImplementedException();
@@ -475,7 +431,7 @@ internal class RedeemVoucherParser : TransactionParserPart {
if (context.CurrentSystem == null) { if (context.CurrentSystem == null) {
transactions.AddIncomplete(new Vouchers(), transactions.AddIncomplete(new Vouchers(),
"Could not find out where the vouchers were redeemed" "Could not find out where the vouchers were redeemed", e
); );
return; return;
} }
@@ -483,123 +439,241 @@ internal class RedeemVoucherParser : TransactionParserPart {
List<Faction>? current_factions = context.GetFactions(context.CurrentSystem); List<Faction>? current_factions = context.GetFactions(context.CurrentSystem);
if (current_factions == null) { if (current_factions == null) {
transactions.AddIncomplete(new Vouchers(), transactions.AddIncomplete(new Vouchers(),
"Current system factions are unknown, so vouchers were ineffective"); "Current system factions are unknown, so vouchers were ineffective", e);
return;
} }
foreach (string faction in entry.Factions) { foreach (string faction in entry.Factions) {
if (current_factions.Find(x => string.Compare(x.Name, faction, true) == 0) == null) { bool relevantBond = false;
transactions.AddIncomplete(new Vouchers(), string relevantFaction = faction;
string.Format("Vouchers for {0} were ignored in {1} since said " +
"faction is not present here", faction, context.CurrentSystem) if (string.Compare(faction, Factions.PilotsFederationVouchers) == 0) {
); // Target faction is pilots' federation, so we assume thargoid bonks
continue; // Also assign this combat bond to the Pilots Federation
relevantFaction = Factions.PilotsFederation;
relevantBond = true;
} }
if (current_factions != null && !relevantBond) {
// If we have local factions, and it ain't thargoid bonds see if the bonds were
// useful in the current system
if (current_factions.Find(x => string.Compare(x.Name, faction, true) == 0) != null) {
relevantBond = true;
} else {
transactions.AddIncomplete(new Vouchers(),
string.Format("Vouchers for \"{0}\" had no effect in \"{1}\" since said " +
"faction is not present there", faction, context.CurrentSystem), e
);
}
}
if (relevantBond) {
transactions.Add(new Vouchers(entry) { transactions.Add(new Vouchers(entry) {
System = context.CurrentSystem, System = context.CurrentSystem,
Station = context.CurrentStation, Station = context.CurrentStation,
Faction = faction, Faction = relevantFaction,
ControllingFaction = context.ControllingFaction, ControllingFaction = context.ControllingFaction,
IsLegacy = context.IsLegacy,
}); });
} }
} }
}
} }
internal class SellMicroResourcesParser : TransactionParserPart { internal class SellMicroResourcesParser : ITransactionParserPart {
public void Parse(Entry e, TransactionParserContext context, TransactionList transactions) { public void Parse(Entry e, TransactionParserContext context, TransactionParserOptions options, TransactionList transactions) {
SellMicroResourcesEntry? entry = e as SellMicroResourcesEntry; SellMicroResourcesEntry? entry = e as SellMicroResourcesEntry;
if (entry == null) { if (entry == null) {
throw new NotImplementedException(); throw new NotImplementedException();
} }
if (context.StationOwner == null) {
transactions.AddIncomplete(
new SellMicroResources(),
"Could not discern the station owner for micro resources sell.",
e);
return;
}
transactions.Add(new SellMicroResources(entry) { transactions.Add(new SellMicroResources(entry) {
System = context.CurrentSystem, System = context.CurrentSystem,
Station = context.CurrentStation, Station = context.CurrentStation,
Faction = context.ControllingFaction, Faction = context.StationOwner,
IsLegacy = context.IsLegacy,
}); });
} }
} }
internal class SearchAndRescueParser : TransactionParserPart { internal class SearchAndRescueParser : ITransactionParserPart {
public void Parse(Entry e, TransactionParserContext context, TransactionList transactions) { public void Parse(Entry e, TransactionParserContext context, TransactionParserOptions options, TransactionList transactions) {
SearchAndRescueEntry? entry = e as SearchAndRescueEntry; SearchAndRescueEntry? entry = e as SearchAndRescueEntry;
if (entry == null) { if (entry == null) {
throw new NotImplementedException(); throw new NotImplementedException();
} }
if (context.StationOwner == null) {
transactions.AddIncomplete(
new OrganicData(),
"Could not discern the station owner for S&R operations.",
e);
return;
}
transactions.Add(new SearchAndRescue(entry) { transactions.Add(new SearchAndRescue(entry) {
Faction = context.ControllingFaction,
Station = context.CurrentStation,
System = context.CurrentSystem, System = context.CurrentSystem,
Station = context.CurrentStation,
Faction = context.StationOwner,
IsLegacy = context.IsLegacy,
}); });
} }
} }
internal class MarketBuyParser : TransactionParserPart { internal class FactionKillBondParser : ITransactionParserPart {
public void Parse(Entry e, TransactionParserContext context, TransactionList transactions) { public void Parse(Entry e, TransactionParserContext context, TransactionParserOptions options, TransactionList transactions) {
MarketBuyEntry? entry = e as MarketBuyEntry;
if (entry == null) {
throw new NotImplementedException();
}
context.BoughtCargo(entry.Type, entry.BuyPrice);
transactions.Add(new BuyCargo(entry) {
Faction = context.ControllingFaction,
Station = context.CurrentStation,
System = context.CurrentSystem,
});
}
}
internal class MarketSellParser : TransactionParserPart {
public void Parse(Entry e, TransactionParserContext context, TransactionList transactions) {
long profit = 0;
MarketSellEntry? entry = e as MarketSellEntry;
if (entry == null) {
throw new NotImplementedException();
}
if (entry.Type == null) {
throw new InvalidJournalEntryException("market sell contains no cargo type");
}
if (context.BuyCost.ContainsKey(entry.Type)) {
long avg = context.BuyCost[entry.Type];
profit = (long)entry.TotalSale - (avg * entry.Count);
}
transactions.Add(new SellCargo(entry) {
Faction = context.ControllingFaction,
Station = context.CurrentStation,
System = context.CurrentSystem,
Profit = profit,
});
}
}
internal class FactionKillBondParser : TransactionParserPart {
public void Parse(Entry e, TransactionParserContext context, TransactionList transactions) {
FactionKillBondEntry? entry = e as FactionKillBondEntry; FactionKillBondEntry? entry = e as FactionKillBondEntry;
if (entry == null) { if (entry == null) {
throw new NotImplementedException(); throw new NotImplementedException();
} }
if (string.Compare(entry.VictimFaction, Thargoid.ThargoidFaction) == 0) { if (Factions.IsThargoid(entry.VictimFaction)) {
// Thargoid bonk // Thargoid bonk
transactions.Add(new ThargoidKill(entry)); transactions.Add(new ThargoidKill(entry) {
System = context.CurrentSystem,
Faction = Factions.PilotsFederation,
Station = context.CurrentStation,
IsLegacy = context.IsLegacy,
});
ThargoidVessel vessel = Thargoid.GetVesselByPayout(entry.Reward);
if (vessel != ThargoidVessel.Unknown) {
if (vessel == ThargoidVessel.Scout) {
++context.ThargoidScoutKills;
} else {
++context.ThargoidInterceptorKills;
}
}
// We are done
return;
}
context.RecordCombatBond(entry);
}
}
internal class EmbarkDisembarkParser : ITransactionParserPart {
public void Parse(Entry e, TransactionParserContext context, TransactionParserOptions options, TransactionList transactions) {
if (e.Is(Events.Embark)) {
context.IsOnFoot = false;
} else if (e.Is(Events.Disembark)) {
context.IsOnFoot = true;
} }
} }
} }
internal class SupercruiseEntryParser : ITransactionParserPart {
public void Parse(Entry entry, TransactionParserContext context, TransactionParserOptions options, TransactionList transactions) {
// After a super cruise entry we are no longer on foot.
context.IsOnFoot = false;
context.DiscernCombatZone(transactions, entry);
context.ResetCombatZone();
// Supercruise entry means you left the current local instance
context.LeftInstance();
}
}
internal class ShutdownParser : ITransactionParserPart {
public void Parse(Entry entry, TransactionParserContext context, TransactionParserOptions options, TransactionList transactions) {
context.DiscernCombatZone(transactions, entry);
context.ResetCombatZone();
// Shutdown (logout) means you left the instance
context.LeftInstance();
}
}
internal class CapShipBondParser : ITransactionParserPart {
public void Parse(Entry entry, TransactionParserContext context, TransactionParserOptions options, TransactionList transactions) {
if (entry.GetType() != typeof(CapShipBondEntry)) {
return;
}
context.HaveSeenCapShip = true;
}
}
internal class FileHeaderParser : ITransactionParserPart {
public void Parse(Entry entry, TransactionParserContext context, TransactionParserOptions options, TransactionList transactions) {
FileHeaderEntry? fileheader = entry as FileHeaderEntry;
if (fileheader == null) {
return;
}
context.IsLegacy = fileheader.IsLegacy;
}
}
internal class ReceiveTextParser : ITransactionParserPart {
public void Parse(Entry entry, TransactionParserContext context, TransactionParserOptions options, TransactionList transactions) {
ReceiveTextEntry? receivetext = entry as ReceiveTextEntry;
if (receivetext == null) {
return;
}
if (string.Compare(receivetext.Channel, Channels.NPC) != 0) {
return;
}
if (string.Compare(receivetext.NPCCategory, NPCs.AXMilitary) == 0) {
context.HaveSeenAXWarzoneNPC = true;
}
}
}
internal class DiedParser : ITransactionParserPart {
public void Parse(Entry entry, TransactionParserContext context, TransactionParserOptions options, TransactionList transactions) {
// Death only matters in ship. On foot you can just redeploy with the dropship.
if (context.IsOnFoot) {
return;
}
// You can't complete a combat zone if you die in it. Others might keep it open
// for you, but still you will not have completed it unless you jump back in.
context.ResetCombatZone();
// Dying also moves you back to another instance
context.LeftInstance();
}
}
internal class DropshipDeployParser : ITransactionParserPart {
public void Parse(Entry entry, TransactionParserContext context, TransactionParserOptions options, TransactionList transactions) {
// On drop ship deploy we are now on foot
context.IsOnFoot = true;
}
}
internal class CommanderParser : ITransactionParserPart {
public void Parse(Entry entry, TransactionParserContext context, TransactionParserOptions options, TransactionList transactions) {
// A commander entry happens when you log out, and log back in again
// for example when switching from Open, to Solo or PG.
context.DiscernCombatZone(transactions, entry);
context.ResetCombatZone();
}
}
public class TransactionParser { public class TransactionParser {
private static Dictionary<string, TransactionParserPart> ParserParts { get; } = new() private static Dictionary<string, ITransactionParserPart> ParserParts { get; } = new()
{ {
{ Events.ApproachSettlement, new ApproachSettlementParser() },
{ Events.CapShipBond, new CapShipBondParser() },
{ Events.Commander, new CommanderParser() },
{ Events.CommitCrime, new CommitCrimeParser() }, { Events.CommitCrime, new CommitCrimeParser() },
{ Events.Died, new DiedParser() },
{ Events.Disembark, new EmbarkDisembarkParser() },
{ Events.Docked, new DockedParser() }, { Events.Docked, new DockedParser() },
{ Events.DropshipDeploy, new DropshipDeployParser() },
{ Events.Embark, new EmbarkDisembarkParser() },
{ Events.FactionKillBond, new FactionKillBondParser() }, { Events.FactionKillBond, new FactionKillBondParser() },
{ Events.FileHeader, new FileHeaderParser() },
{ Events.FSDJump, new FSDJumpParser() }, { Events.FSDJump, new FSDJumpParser() },
{ Events.Location, new LocationParser() }, { Events.Location, new LocationParser() },
{ Events.MarketBuy, new MarketBuyParser() }, { Events.MarketBuy, new MarketBuyParser() },
@@ -607,16 +681,42 @@ public class TransactionParser {
{ Events.MissionAccepted, new MissionAcceptedParser() }, { Events.MissionAccepted, new MissionAcceptedParser() },
{ Events.MissionCompleted, new MissionCompletedParser() }, { Events.MissionCompleted, new MissionCompletedParser() },
{ Events.MissionFailed, new MissionFailedParser() }, { Events.MissionFailed, new MissionFailedParser() },
{ Events.Missions, new MissionsParser() },
{ Events.MultiSellExplorationData, new MultiSellExplorationDataParser() }, { Events.MultiSellExplorationData, new MultiSellExplorationDataParser() },
{ Events.ReceiveText, new ReceiveTextParser() },
{ Events.RedeemVoucher, new RedeemVoucherParser() }, { Events.RedeemVoucher, new RedeemVoucherParser() },
{ Events.SearchAndRescue, new SearchAndRescueParser() }, { Events.SearchAndRescue, new SearchAndRescueParser() },
{ Events.SellExplorationData, new SellExplorationDataParser() }, { Events.SellExplorationData, new SellExplorationDataParser() },
{ Events.SellMicroResources, new SellMicroResourcesParser() }, { Events.SellMicroResources, new SellMicroResourcesParser() },
{ Events.SellOrganicData, new SellOrganicDataParser() }, { Events.SellOrganicData, new SellOrganicDataParser() },
{ Events.ShipTargeted, new ShipTargetedParser() }, { Events.ShipTargeted, new ShipTargetedParser() },
{ Events.Shutdown, new ShutdownParser() },
{ Events.SupercruiseDestinationDrop, new SupercruiseDestinationDropParser() },
{ Events.SupercruiseEntry, new SupercruiseEntryParser() },
}; };
public bool IsRelevant(string entry) {
return ParserParts.ContainsKey(entry);
}
public bool IsRelevant(Entry e) {
if (e.Event == null) {
return false;
}
return IsRelevant(e.Event);
}
/// <summary>
/// Parses a list of entries with default options.
/// </summary>
/// <param name="entries"></param>
/// <returns></returns>
public List<Transaction>? Parse(IEnumerable<Entry> entries) { public List<Transaction>? Parse(IEnumerable<Entry> entries) {
TransactionParserOptions defaultOptions = new();
return Parse(entries, defaultOptions);
}
public List<Transaction>? Parse(IEnumerable<Entry> entries, TransactionParserOptions options) {
TransactionList transactions = new(); TransactionList transactions = new();
TransactionParserContext context = new(); TransactionParserContext context = new();
@@ -629,8 +729,8 @@ public class TransactionParser {
continue; continue;
} }
TransactionParserPart transactionParserPart = ParserParts[entry.Event]; ITransactionParserPart transactionParserPart = ParserParts[entry.Event];
transactionParserPart.Parse(entry, context, transactions); transactionParserPart.Parse(entry, context, options, transactions);
} }
return transactions.ToList(); return transactions.ToList();

View File

@@ -0,0 +1,320 @@
using EDPlayerJournal.Entries;
namespace EDPlayerJournal.BGS;
internal class TransactionParserContext {
/// <summary>
/// Name of the current system the player is in.
/// </summary>
public string? CurrentSystem { get; set; }
/// <summary>
/// 64 bit address of the current system.
/// </summary>
public ulong? CurrentSystemAddress { get; set; }
/// <summary>
/// Controlling faction of the current system.
/// </summary>
public string? ControllingFaction { get; set; }
/// <summary>
/// Name of the current station the player is docked at.
/// </summary>
public string? CurrentStation { get; set; }
/// <summary>
/// Faction that owns the current station.
/// </summary>
public string? StationOwner { get; set; }
/// <summary>
/// Whether the player is currently on foot, or in an SRV/ship.
/// </summary>
public bool IsOnFoot { get; set; } = false;
/// <summary>
/// Type of the current instance after a SupercruiseDestinationDropEntry.
/// This is null if there is no current instance, or the current instance
/// is not known.
/// </summary>
public string? CurrentInstanceType { get; set; } = null;
/// <summary>
/// Last faction that awarded the player with combat bonds.
/// </summary>
public string? LastRecordedAwardingFaction { get; set; }
/// <summary>
/// Highest combat bond seen so far.
/// </summary>
public ulong? HighestCombatBond { get; set; }
public bool HaveSeenCapShip { get; set; } = false;
public bool HaveSeenAlliedCaptain { get; set; } = false;
public bool HaveSeenEnemyCaptain { get; set; } = false;
public bool HaveSeenSpecOps { get; set; } = false;
public bool HaveSeenAlliedCorrespondent { get; set; } = false;
public bool HaveSeenEnemyCorrespondent { get; set; } = false;
/// <summary>
/// Current Odyssey settlement.
/// </summary>
public string? Settlement { get; set; } = null;
/// <summary>
/// Returns true if the current session is legacy
/// </summary>
public bool IsLegacy { get; set; } = false;
/// <summary>
/// How many on foot kills were done.
/// </summary>
public ulong OnFootKills { get; set; } = 0;
/// <summary>
/// How many ship kills were done.
/// </summary>
public ulong ShipKills { get; set; } = 0;
/// <summary>
/// Thargoid scouts killed
/// </summary>
public ulong ThargoidScoutKills { get; set; } = 0;
/// <summary>
/// Thargoid interceptor kills
/// </summary>
public ulong ThargoidInterceptorKills { get; set; } = 0;
/// <summary>
/// Whether we have seen an AX warzone NPC talk to us with ReceiveText
/// </summary>
public bool HaveSeenAXWarzoneNPC { get; set; } = false;
/// <summary>
/// A list of accepted missions index by their mission ID
/// </summary>
public Dictionary<ulong, Mission> AcceptedMissions { get; } = new();
public Dictionary<ulong, Location> AcceptedMissionLocation { get; } = new();
/// <summary>
/// A way to lookup a system by its system id
/// </summary>
public Dictionary<ulong, string> SystemsByID { get; } = new();
/// <summary>
/// A list of factions present in the given star system
/// </summary>
public Dictionary<string, List<Faction>> SystemFactions { get; } = new();
/// <summary>
/// To which faction a given named NPC belonged to.
/// </summary>
public Dictionary<string, string> NPCFaction { get; } = new();
/// <summary>
/// Buy costs for a given commodity
/// </summary>
public Dictionary<string, long> BuyCost = new();
/// <summary>
/// Called when the player leaves an instance, either through logout, FSD jump or
/// supercruise instance.
/// </summary>
public void LeftInstance() {
CurrentInstanceType = null;
Settlement = null;
}
public void DiscernCombatZone(TransactionList transactions, Entry e) {
string? grade = CombatZones.DifficultyLow;
string cztype;
ulong highest = HighestCombatBond ?? 0;
string? faction = LastRecordedAwardingFaction;
if (HighestCombatBond == null &&
LastRecordedAwardingFaction == null &&
HaveSeenAXWarzoneNPC == false &&
CurrentInstanceType == null) {
return;
}
if (OnFootKills > 0 || IsOnFoot == true) {
cztype = CombatZones.GroundCombatZone;
// High on foot combat zones have enforcers that bring 80k a pop
if (highest >= 60000) {
grade = CombatZones.DifficultyHigh;
} else if (highest >= 30000) {
// In medium conflict zones, the enforcers are worth 30k
grade = CombatZones.DifficultyMedium;
} else {
grade = CombatZones.DifficultyLow;
}
} else if (CurrentInstanceType != null) {
if (!Instances.IsWarzone(CurrentInstanceType)) {
return;
}
if (LastRecordedAwardingFaction == null &&
Instances.IsHumanWarzone(CurrentInstanceType)) {
transactions.AddIncomplete(new CombatZone(),
"Could not discern for whom you fought for, " +
"as it seems you haven't killed anyone in the ship combat zone.",
e);
return;
}
// If we have information about the current instance being a warship use that
// information to determine the warzone.
if (Instances.IsInstance(CurrentInstanceType, Instances.WarzoneLow)) {
cztype = CombatZones.ShipCombatZone;
grade = CombatZones.DifficultyLow;
} else if (Instances.IsInstance(CurrentInstanceType, Instances.WarzoneMedium)) {
cztype = CombatZones.ShipCombatZone;
grade = CombatZones.DifficultyMedium;
} else if (Instances.IsInstance(CurrentInstanceType, Instances.WarzoneHigh)) {
cztype = CombatZones.ShipCombatZone;
grade = CombatZones.DifficultyHigh;
} else if (Instances.IsInstance(CurrentInstanceType, Instances.WarzoneThargoidLow)) {
cztype = CombatZones.AXCombatZone;
grade = CombatZones.DifficultyLow;
} else if (Instances.IsInstance(CurrentInstanceType, Instances.WarzoneThargoidMedium)) {
cztype = CombatZones.AXCombatZone;
grade = CombatZones.DifficultyMedium;
} else if (Instances.IsInstance(CurrentInstanceType, Instances.WarzoneThargoidHigh)) {
cztype = CombatZones.AXCombatZone;
grade = CombatZones.DifficultyHigh;
} else if (Instances.IsInstance(CurrentInstanceType, Instances.WarzoneThargoidVeryHigh)) {
cztype = CombatZones.AXCombatZone;
grade = CombatZones.DifficultyVeryHigh;
} else {
transactions.AddIncomplete(new CombatZone(),
"Unknown conflict zone difficulty",
e);
return;
}
} else if (ShipKills > 0 && !IsOnFoot) {
// Ship combat zones can be identified by the amount of kills
if (ShipKills > 20) {
grade = CombatZones.DifficultyHigh;
} else if (ShipKills > 10) {
grade = CombatZones.DifficultyMedium;
}
// Cap ship, means a high conflict zone
if (HaveSeenCapShip) {
grade = CombatZones.DifficultyHigh;
} else {
int warzoneNpcs = new List<bool>() {
HaveSeenEnemyCaptain,
HaveSeenEnemyCorrespondent,
HaveSeenSpecOps
}
.Where(x => x == true)
.Count()
;
if (warzoneNpcs >= 1 && grade == CombatZones.DifficultyLow) {
grade = CombatZones.DifficultyMedium;
}
}
cztype = CombatZones.ShipCombatZone;
} else if ((ThargoidScoutKills > 0 && ThargoidInterceptorKills > 0) ||
HaveSeenAXWarzoneNPC == true) {
// Could be a thargoid combat zones if interceptors and scouts are there
cztype = CombatZones.AXCombatZone;
// Still unknown
grade = null;
} else {
transactions.AddIncomplete(new CombatZone(), "Failed to discern combat zone type", e);
return;
}
CombatZone zone = new CombatZone() {
System = CurrentSystem,
Faction = faction,
IsLegacy = IsLegacy,
Settlement = Settlement,
Grade = grade,
Type = cztype,
// Sad truth is, if HaveSeenXXX is false, we just don't know for certain
CapitalShip = HaveSeenCapShip ? true : null,
SpecOps = HaveSeenSpecOps ? true : null,
EnemyCorrespondent = HaveSeenEnemyCorrespondent ? true : null,
AlliedCorrespondent = HaveSeenAlliedCorrespondent ? true : null,
EnemyCaptain = HaveSeenEnemyCaptain ? true : null,
AlliedCaptain = HaveSeenAlliedCaptain ? true : null,
};
zone.Entries.Add(e);
transactions.Add(zone);
}
public void RecordCombatBond(FactionKillBondEntry e) {
if (HighestCombatBond == null || e.Reward > HighestCombatBond) {
HighestCombatBond = e.Reward;
}
LastRecordedAwardingFaction = e.AwardingFaction;
if (IsOnFoot) {
++OnFootKills;
} else {
++ShipKills;
}
}
public void ResetCombatZone() {
HighestCombatBond = null;
HaveSeenCapShip = false;
HaveSeenAlliedCaptain = false;
HaveSeenEnemyCaptain = false;
HaveSeenAlliedCorrespondent = false;
HaveSeenEnemyCorrespondent = false;
HaveSeenSpecOps = false;
LastRecordedAwardingFaction = null;
OnFootKills = 0;
ShipKills = 0;
ThargoidInterceptorKills = 0;
ThargoidScoutKills = 0;
HaveSeenAXWarzoneNPC = false;
}
public void BoughtCargo(string? cargo, long? cost) {
if (cargo == null || cost == null) {
return;
}
BuyCost[cargo] = cost.Value;
}
public List<Faction>? GetFactions(string? system) {
if (system == null || !SystemFactions.ContainsKey(system)) {
return null;
}
return SystemFactions[system];
}
public void MissionAccepted(MissionAcceptedEntry? entry) {
if (entry == null) {
return;
}
MissionAccepted(entry.Mission);
}
public void MissionAccepted(Mission? mission) {
if (CurrentSystem == null || CurrentSystemAddress == null) {
throw new Exception("Mission accepted without knowing where.");
}
if (mission == null) {
throw new Exception("Mission is null");
}
AcceptedMissions.TryAdd(mission.MissionID, mission);
Location location = new() {
StarSystem = CurrentSystem,
SystemAddress = CurrentSystemAddress.Value,
Station = (CurrentStation ?? ""),
};
AcceptedMissionLocation.TryAdd(mission.MissionID, location);
}
}

View File

@@ -0,0 +1,19 @@
using EDPlayerJournal.Entries;
namespace EDPlayerJournal.BGS;
internal interface ITransactionParserPart {
/// <summary>
/// Parse a given entry of the entry type specified when declaring to implement this
/// interface. You must add your transaction to the passed list in case you did have
/// enough information to parse one or more. You may update the parser context
/// with new information in case the entry yielded new information.
/// Throw an exception if there was an error or a malformed entry. If you have an
/// incomplete entry, i.e. not enough information to complete one, add an
/// IncompleteTransaction to the list
/// </summary>
/// <param name="entry">The entry to parse</param>
/// <param name="context">Parsing context that may contain useful information</param>
/// <param name="transactions">List of parsed transactions</param>
public void Parse(Entry entry, TransactionParserContext context, TransactionParserOptions options, TransactionList transactions);
}

View File

@@ -20,8 +20,8 @@ public class Vouchers : Transaction {
} }
return Entries return Entries
.OfType<RedeemVoucherEntry>() .OfType<RedeemVoucherEntry>()
.Where(x => x.FactionBounties.ContainsKey(Faction)) .ToList()
.Sum(x => x.FactionBounties[Faction]) .Sum(x => (long)x.GetBountyForFaction(Faction))
; ;
} }
} }
@@ -39,9 +39,9 @@ public class Vouchers : Transaction {
.First(); .First();
if (v == null) { if (v == null) {
return null; return null;
} else if (v == "CombatBond") { } else if (v == Bonds.CombatBond) {
type = "Combat Bond"; type = "Combat Bond";
} else if (v == "bounty") { } else if (v == Bonds.Bounty) {
type = "Bounty"; type = "Bounty";
} else { } else {
type = CultureInfo.CurrentCulture.TextInfo.ToTitleCase(v); type = CultureInfo.CurrentCulture.TextInfo.ToTitleCase(v);

13
EDPlayerJournal/Bonds.cs Normal file
View File

@@ -0,0 +1,13 @@
namespace EDPlayerJournal;
public class Bonds {
/// <summary>
/// Internal string name for combat bonds.
/// </summary>
public static readonly string CombatBond = "CombatBond";
/// <summary>
/// Internal string name for bounties.
/// </summary>
public static readonly string Bounty = "bounty";
}

View File

@@ -0,0 +1,8 @@
namespace EDPlayerJournal;
public class Channels {
/// <summary>
/// Channel NPCs use to talk to the commander.
/// </summary>
public static readonly string NPC = "npc";
}

View File

@@ -0,0 +1,64 @@
namespace EDPlayerJournal;
/// <summary>
/// Static strings related to combat zones
/// </summary>
public class CombatZones {
/// <summary>
/// Type string for ground combat zone
/// </summary>
public static readonly string GroundCombatZone = "Ground";
/// <summary>
/// Type string for ship combat zones
/// </summary>
public static readonly string ShipCombatZone = "Ship";
/// <summary>
/// AX combat zone
/// </summary>
public static readonly string AXCombatZone = "AX";
/// <summary>
/// Difficulty low
/// </summary>
public static readonly string DifficultyLow = "Low";
/// <summary>
/// Difficulty medium
/// </summary>
public static readonly string DifficultyMedium = "Medium";
/// <summary>
/// Difficulty high
/// </summary>
public static readonly string DifficultyHigh = "High";
/// <summary>
/// Very high difficulty, so far AX combat zone only
/// </summary>
public static readonly string DifficultyVeryHigh = "Very High";
/// <summary>
/// Returns the given combat zone difficulty as an integer, so it can be sorted.
/// 0 = lowest difficulty, 1 = medium and so forth.
/// </summary>
public static int? DifficultyRank(string? difficulty) {
Dictionary<string, int> ranks = new() {
{ DifficultyLow, 0 },
{ DifficultyMedium, 1 },
{ DifficultyHigh, 2 },
{ DifficultyVeryHigh, 3 }
};
if (difficulty == null ) {
return null;
}
if (ranks.TryGetValue(difficulty, out int rank)) {
return rank;
}
return null;
}
}

View File

@@ -34,4 +34,16 @@ public class Credits {
return string.Format("{0} CR", amount.ToString("N", format)); return string.Format("{0} CR", amount.ToString("N", format));
} }
public static string FormatMillions(long amount) {
double millions = (amount / 1000000.0);
if (amount >= 100000) {
return string.Format("{0:0.0}M", millions);
} else if (amount >= 10000) {
return string.Format("{0:0.00}M", millions);
}
return string.Format("{0}", amount);
}
} }

View File

@@ -1,27 +1,28 @@
using EDPlayerJournal.BGS; namespace EDPlayerJournal;
using static System.Runtime.InteropServices.JavaScript.JSType;
using System.Collections.ObjectModel;
using System.Diagnostics.Metrics;
using System.Net.NetworkInformation;
using System.Numerics;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System;
namespace EDPlayerJournal;
public class EnglishMissionNames { public class EnglishMissionNames {
public static Dictionary<string, string> MissionNames { get; } = new Dictionary<string, string>() { public static Dictionary<string, string> MissionNames { get; } = new Dictionary<string, string>() {
{"Chain_FindThePirateLord_name", "Assassination (Pirate Lord) (Chain)"}, {"Chain_FindThePirateLord_name", "Assassination (Pirate Lord) (Chain)"},
{"Chain_RegainFooting_name", "Regain Footing (Chain)"}, {"Chain_RegainFooting_name", "Regain Footing (Chain)"},
{"Chain_SalvageJustice_name", "Assassination (Legal) (Chain)"}, {"Chain_SalvageJustice_name", "Assassination (Legal) (Chain)"},
{"Mission_Altruism_Bust_name", "Donate (Bust)" },
{"Mission_Altruism_CivilUnrest_name", "Donate (Civil Unrest)" },
{"Mission_Altruism_name", "Donate"}, {"Mission_Altruism_name", "Donate"},
{"Mission_Altruism_Outbreak_name", "Donate (Outbreak)" },
{"Mission_AltruismCredits_Bust_name", "Donate Credits (Bust)"}, {"Mission_AltruismCredits_Bust_name", "Donate Credits (Bust)"},
{"Mission_AltruismCredits_CivilUnrest_name", "Donate Credits (Civil Unrest)" },
{"Mission_AltruismCredits_Famine_name", "Donate Credits (Famine)"}, {"Mission_AltruismCredits_Famine_name", "Donate Credits (Famine)"},
{"Mission_AltruismCredits_name", "Donate Credits"}, {"Mission_AltruismCredits_name", "Donate Credits"},
{"Mission_AltruismCredits_Outbreak_name", "Donate Credits (Outbreak)" },
{"Mission_Assassinate_Illegal_BLOPS_name", "Assassination (Illegal)"}, {"Mission_Assassinate_Illegal_BLOPS_name", "Assassination (Illegal)"},
{"Mission_Assassinate_Legal_Corporate_name", "Corporate Assassination (Legal)"}, {"MISSION_assassinate_Illegal_Planetary_name", "Assassination (Illegal, Planetary)" },
{"Mission_Assassinate_Legal_Bust_name", "Assassination (Bust, Legal)" },
{"MISSION_assassinate_legal_CivilUnrest_name", "Assassination (Civil Unrest, Legal)" },
{"Mission_Assassinate_Legal_Communism_name", "Assassination (Communism, Legal)" },
{"Mission_Assassinate_Legal_Corporate_name", "Assassination (Corporate, Legal)"},
{"Mission_Assassinate_Legal_War_name", "Assassination (War, Legal)" },
{"Mission_Assassinate_name", "Assassination"}, {"Mission_Assassinate_name", "Assassination"},
{"Mission_Assassinate_Planetary_Expansion_name", "Assassination (Planetary Scan, Expansion)" },
{"Mission_Assassinate_Planetary_name", "Assassination (Planetary Scan)"}, {"Mission_Assassinate_Planetary_name", "Assassination (Planetary Scan)"},
{"Mission_Collect_Bust_name", "Provide (Bust)"}, {"Mission_Collect_Bust_name", "Provide (Bust)"},
{"Mission_Collect_CivilLiberty_name", "Provide (Civil Liberty)"}, {"Mission_Collect_CivilLiberty_name", "Provide (Civil Liberty)"},
@@ -29,8 +30,10 @@ public class EnglishMissionNames {
{"Mission_Collect_Famine_name", "Provide (Famine)"}, {"Mission_Collect_Famine_name", "Provide (Famine)"},
{"Mission_Collect_Industrial_name", "Provide (Industrial)"}, {"Mission_Collect_Industrial_name", "Provide (Industrial)"},
{"Mission_Collect_name", "Provide"}, {"Mission_Collect_name", "Provide"},
{"Mission_Collect_Outbreak_name", "Provide (Outbreak)" },
{"Mission_Collect_RankEmp_name", "Provide (Imperial Navy)"}, {"Mission_Collect_RankEmp_name", "Provide (Imperial Navy)"},
{"Mission_Collect_Retreat_name", "Provide (Retreat)"}, {"Mission_Collect_Retreat_name", "Provide (Retreat)"},
{"Mission_Courier_CivilUnrest_name", "Courier (Divil Unrest)" },
{"Mission_Courier_Democracy_name", "Courier (Democracy)"}, {"Mission_Courier_Democracy_name", "Courier (Democracy)"},
{"Mission_Courier_Elections_name", "Courier (Elections)"}, {"Mission_Courier_Elections_name", "Courier (Elections)"},
{"Mission_Courier_Expansion_name", "Courier (Expansion)"}, {"Mission_Courier_Expansion_name", "Courier (Expansion)"},
@@ -38,8 +41,10 @@ public class EnglishMissionNames {
{"Mission_Courier_Lockdown_name", "Courier (Lockdown)"}, {"Mission_Courier_Lockdown_name", "Courier (Lockdown)"},
{"Mission_Courier_name", "Courier"}, {"Mission_Courier_name", "Courier"},
{"Mission_Courier_RankEmp_name", "Courier (Imperial Navy)"}, {"Mission_Courier_RankEmp_name", "Courier (Imperial Navy)"},
{"Mission_Courier_War_name", "Courier (War)" },
{"Mission_Delivery_Agriculture_name", "Delivery (Agriculture)"}, {"Mission_Delivery_Agriculture_name", "Delivery (Agriculture)"},
{"Mission_Delivery_Boom_name", "Delivery (Boom)"}, {"Mission_Delivery_Boom_name", "Delivery (Boom)"},
{"Mission_Delivery_CivilUnrest_name", "Delivery (Civil Unrest)"},
{"Mission_Delivery_Democracy_name", "Delivery (Democracy)"}, {"Mission_Delivery_Democracy_name", "Delivery (Democracy)"},
{"Mission_Delivery_Investment_name", "Delivery (Investment)"}, {"Mission_Delivery_Investment_name", "Delivery (Investment)"},
{"Mission_Delivery_name", "Delivery"}, {"Mission_Delivery_name", "Delivery"},
@@ -47,6 +52,10 @@ public class EnglishMissionNames {
{"Mission_Delivery_Retreat_name", "Delivery (Retreat)"}, {"Mission_Delivery_Retreat_name", "Delivery (Retreat)"},
{"Mission_DeliveryWing_name", "Delivery (Wing)"}, {"Mission_DeliveryWing_name", "Delivery (Wing)"},
{"Mission_DeliveryWing_War_name", "Delivery (Wing) (War)"}, {"Mission_DeliveryWing_War_name", "Delivery (Wing) (War)"},
{"Mission_Disable_BLOPS_Expansion_name", "Disable Surface Installation (Expansion) (Black Ops)" },
{"MISSION_Disable_BLOPS_name", "Disable Surface Installation (Black Ops)" },
{"MISSION_Disable_name", "Disable Surface Installation" },
{"MISSION_Hack_name", "Hack Surface Installation" },
{"Mission_Hack_BLOPS_Boom_name", "Hack Surface Installation (Boom)"}, {"Mission_Hack_BLOPS_Boom_name", "Hack Surface Installation (Boom)"},
{"Mission_Hack_BLOPS_Elections_name", "Hack Surface Installation (Elections)"}, {"Mission_Hack_BLOPS_Elections_name", "Hack Surface Installation (Elections)"},
{"Mission_Hack_BLOPS_Expansion_name", "Hack Surface Installation (Expansion)"}, {"Mission_Hack_BLOPS_Expansion_name", "Hack Surface Installation (Expansion)"},
@@ -59,29 +68,46 @@ public class EnglishMissionNames {
{"Mission_Massacre_RankEmp_name", "Massacre (Imperial Navy)"}, {"Mission_Massacre_RankEmp_name", "Massacre (Imperial Navy)"},
{"Mission_MassacreWing_Legal_Bust_name", "Massacre (Wing) (Bust)"}, {"Mission_MassacreWing_Legal_Bust_name", "Massacre (Wing) (Bust)"},
{"Mission_MassacreWing_name", "Massacre (Wing)"}, {"Mission_MassacreWing_name", "Massacre (Wing)"},
{"Mission_Mining_name", "Mining" },
{"Mission_OnFoot_Assassination_MB_name", "On Foot Assassination"}, {"Mission_OnFoot_Assassination_MB_name", "On Foot Assassination"},
{"Mission_OnFoot_Assassination_Hard_MB_name", "On Foot Assassination (Flight-Risk)" },
{"Mission_OnFoot_Assassination_Covert_MB_name", "On Foot Assassination (Covert)" },
{"Mission_OnFoot_AssassinationIllegal_MB_name", "On Foot Assassination (Illegal)"}, {"Mission_OnFoot_AssassinationIllegal_MB_name", "On Foot Assassination (Illegal)"},
{"Mission_OnFoot_AssassinationIllegal_NCD_MB_name", "On Foot Assassination (Illegal)" },
{"Mission_OnFoot_Collect_Contact_MB_name", "On Foot Collect"}, {"Mission_OnFoot_Collect_Contact_MB_name", "On Foot Collect"},
{"Mission_OnFoot_Collect_MB_name", "On Foot Collection"}, {"Mission_OnFoot_Collect_MB_name", "On Foot Collection"},
{"Mission_OnFoot_Defence_MacGuffin_MB_StandardCanister_name", "On Foot Cargo Defence" },
{"Mission_OnFoot_Delivery_Contact_MB_name", "On Foot Delivery (Contact)"}, {"Mission_OnFoot_Delivery_Contact_MB_name", "On Foot Delivery (Contact)"},
{"Mission_OnFoot_Hack_Upload_Covert_MB_name", "On Foot Hack (Covert Upload)"}, {"Mission_OnFoot_Hack_Upload_Covert_MB_name", "On Foot Hack (Covert Upload)"},
{"Mission_OnFoot_Hack_Upload_MB_name", "On Foot Hack (Upload)"}, {"Mission_OnFoot_Hack_Upload_MB_name", "On Foot Hack (Upload)"},
{"Mission_OnFoot_Heist_MB_name", "On Foot Heist" },
{"Mission_OnFoot_Heist_POI_MB_name", "On Foot Heist (POI)"}, {"Mission_OnFoot_Heist_POI_MB_name", "On Foot Heist (POI)"},
{"Mission_OnFoot_Massacre_MB_name", "On Foot Massacre" },
{"Mission_OnFoot_MassacreIllegal_MB_name", "On Foot Massacre (Illegal)"},
{"Mission_OnFoot_Onslaught_MB_name", "On Foot Onslaught"}, {"Mission_OnFoot_Onslaught_MB_name", "On Foot Onslaught"},
{"Mission_OnFoot_Onslaught_Offline_MB_name", "On Foot Onslaught (Offline)"}, {"Mission_OnFoot_Onslaught_Offline_MB_name", "On Foot Onslaught (Offline)"},
{"Mission_OnFoot_ProductionHeist_Covert_MB_name", "On Foot Production Heist (Covert)"}, {"Mission_OnFoot_ProductionHeist_Covert_MB_name", "On Foot Production Heist (Covert)"},
{"Mission_OnFoot_ProductionHeist_MB_name", "On Foot Production Heist"}, {"Mission_OnFoot_ProductionHeist_MB_name", "On Foot Production Heist"},
{"Mission_OnFoot_Reboot_MB_name", "On Foot Reboot"}, {"Mission_OnFoot_Reboot_MB_name", "On Foot Reboot"},
{"Mission_OnFoot_RebootRestore_MB_name", "On Foot Reboot/Restore"}, {"Mission_OnFoot_RebootRestore_MB_name", "On Foot Reboot/Restore"},
{"Mission_OnFoot_Sabotage_Production_Covert_MB_name", "On Foot Sabotage Production (Covert)"}, {"Mission_OnFoot_Sabotage_Power_MB_name", "On Foot Power Sabotage"},
{"Mission_OnFoot_Sabotage_Power_Covert_MB_name", "On Foot Power Sabotage (Covert)"},
{"Mission_OnFoot_Sabotage_Production_MB_name", "On Foot Production Sabotage"},
{"Mission_OnFoot_Sabotage_Production_Covert_MB_name", "On Foot Production Sabotage (Covert)"},
{"Mission_OnFoot_Salvage_MB_name", "On Foot Salvage"}, {"Mission_OnFoot_Salvage_MB_name", "On Foot Salvage"},
{"Mission_OnFoot_SalvageIllegal_MB_name", "On Foot Salvage (Illegal)"}, {"Mission_OnFoot_SalvageIllegal_MB_name", "On Foot Salvage (Illegal)"},
{"Mission_PassengerBulk_AIDWORKER_ARRIVING", "Aid Workers Seeking Transport"}, {"Mission_OnFoot_Smuggle_Contact_MB_name", "On Foot Smuggling" },
{"Mission_PassengerBulk_AIDWORKER_ARRIVING", "Seeking Transport (Aid Workers)"},
{"Mission_PassengerBulk_name", "Seeking Transport" },
{"Mission_PassengerVIP", "Passenger (VIP)"}, {"Mission_PassengerVIP", "Passenger (VIP)"},
{"Mission_PassengerVIP_Criminal_BOOM_name", "Passenger Criminal (VIP) (Boom)"}, {"Mission_PassengerVIP_Criminal_BOOM_name", "Passenger Criminal (VIP) (Boom)"},
{"Mission_PassengerVIP_name", "Passenger (VIP)"}, {"Mission_PassengerVIP_name", "Passenger (VIP)"},
{"Mission_PassengerVIP_Scientist_FAMINE_name", "Passenger Scientist (VIP) (Famine)"}, {"Mission_PassengerVIP_Scientist_FAMINE_name", "Passenger Scientist (VIP) (Famine)"},
{"Mission_PassengerVIP_Tourist_BOOM_name", "Passenger Tourist (VIP) (Boom)"}, {"Mission_PassengerVIP_Tourist_BOOM_name", "Passenger Tourist (VIP) (Boom)"},
{"Mission_Rescue_Elections_name", "Liberate Hostages (Election)" },
{"Mission_Rescue_name", "Liberate Hostages" },
{"Mission_Rescue_Planet_Expansion_name", "Planet Rescue (Expansion)" },
{"Mission_Rescue_Planet_Retreat_name", "Planet Rescue (Retreat)" },
{"Mission_Rescue_Planet_name", "Planet Rescue"}, {"Mission_Rescue_Planet_name", "Planet Rescue"},
{"MISSION_Salvage_CivilUnrest_name", "Salvage (Civil Unrest)"}, {"MISSION_Salvage_CivilUnrest_name", "Salvage (Civil Unrest)"},
{"MISSION_Salvage_Expansion_name", "Salvage (Expansion)"}, {"MISSION_Salvage_Expansion_name", "Salvage (Expansion)"},
@@ -93,6 +119,22 @@ public class EnglishMissionNames {
{"MISSION_Scan_name", "Scan"}, {"MISSION_Scan_name", "Scan"},
{"Mission_Sightseeing_Criminal_FAMINE_name", "Sightseeing (Criminal) (Famine)"}, {"Mission_Sightseeing_Criminal_FAMINE_name", "Sightseeing (Criminal) (Famine)"},
{"Mission_Sightseeing_name", "Sightseeing"}, {"Mission_Sightseeing_name", "Sightseeing"},
{"Mission_Smuggle_Anarchy_name", "Smuggling (Anarchy)" },
{"Mission_TW_Massacre_Basilisk_Plural_name", "Kill Basilisks" },
{"Mission_TW_Massacre_Basilisk_Singular_name", "Kill Basilisk" },
{"Mission_TW_Massacre_Cyclops_Plural_name", "Kill Cyclops" },
{"Mission_TW_Massacre_Cyclops_Singular_name", "Kill Cyclop" },
{"Mission_TW_Massacre_Hydra_Plural_name", "Kill Hydras" },
{"Mission_TW_Massacre_Hydra_Singular_name", "Kill Hydra" },
{"Mission_TW_Massacre_Medusa_Plural_name", "Kill Medusas" },
{"Mission_TW_Massacre_Medusa_Singular_name", "Kill Medusa" },
{"Mission_TW_Massacre_Scout_Plural_name", "Kill Scouts" },
{"Mission_TW_OnFoot_Reboot_Occupied_MB_name", "Reboot Settlement (Thargoid)" },
{"Mission_TW_OnFoot_Reboot_NR_name", "Reboot Settlement (Thargoid)" },
{"Mission_TW_OnFoot_Reboot_MB_name", "Reboot Settlement (Thargoid)" },
{"Mission_TW_PassengerEvacuation_Burning_name", "Passenger Evacuation (Significant Damage)" },
{"Mission_TW_PassengerEvacuation_UnderAttack_name", "Passenger Evacuation (Thargoid Invasion)" },
{"Mission_TW_Rescue_UnderAttack_name", "Rescue (Thargoid Attack)" },
}; };
public static string? Translate(string name) { public static string? Translate(string name) {

View File

@@ -0,0 +1,48 @@
namespace EDPlayerJournal.Entries;
public class ApproachSettlementEntry : Entry {
/// <summary>
/// Settlement name
/// </summary>
public string? Name { get; set; } = null;
/// <summary>
/// Market ID of the settlement
/// </summary>
public long? MarketID { get; set; } = null;
/// <summary>
/// System ID
/// </summary>
public long? SystemAddress { get; set; } = null;
/// <summary>
/// Body ID
/// </summary>
public long? BodyID { get; set; } = null;
/// <summary>
/// Name of the planet
/// </summary>
public string? BodyName { get; set; } = null;
/// <summary>
/// Planet latitude
/// </summary>
public double Latitude { get; set; } = 0.0;
/// <summary>
/// Planet longitude
/// </summary>
public double Longitude { get; set; } = 0.0;
protected override void Initialise() {
Name = JSON.Value<string?>("Name");
MarketID = JSON.Value<long?>("MarketID");
SystemAddress = JSON.Value<long?>("SystemID");
BodyID = JSON.Value<long?>("BodyID");
BodyName = JSON.Value<string?>("BodyName");
Longitude = JSON.Value<double?>("Longitude") ?? 0.0;
Latitude = JSON.Value<double?>("Latitude") ?? 0.0;
}
}

View File

@@ -0,0 +1,24 @@
namespace EDPlayerJournal.Entries;
public class CapShipBondEntry : Entry {
/// <summary>
/// Reward gained
/// </summary>
public ulong Reward { get; set; } = 0;
/// <summary>
/// Victim faction, i.e. to whom the cap ship belongs
/// </summary>
public string? VictimFaction { get; set; }
/// <summary>
/// Awarding faction.
/// </summary>
public string? AwardingFaction { get; set; }
protected override void Initialise() {
Reward = JSON.Value<ulong?>("Reward") ?? 0;
VictimFaction = JSON.Value<string?>("VictimFaction");
AwardingFaction = JSON.Value<string?>("AwardingFaction");
}
}

View File

@@ -19,4 +19,12 @@ public class CommitCrimeEntry : Entry {
public bool IsMurder { public bool IsMurder {
get { return CrimeType?.CompareTo(CrimeTypes.Murder) == 0 || CrimeType?.CompareTo(CrimeTypes.OnFootMurder) == 0; } get { return CrimeType?.CompareTo(CrimeTypes.Murder) == 0 || CrimeType?.CompareTo(CrimeTypes.OnFootMurder) == 0; }
} }
public bool IsCrime(string crimetype) {
if (CrimeType == null) {
return false;
}
return string.Compare(CrimeType, crimetype) == 0;
}
} }

View File

@@ -0,0 +1,66 @@
namespace EDPlayerJournal.Entries;
public class DisembarkEntry : Entry {
/// <summary>
/// Disembarked into an SRV?
/// </summary>
public bool SRV { get; set; } = false;
/// <summary>
/// Taxi?
/// </summary>
public bool Taxi { get; set; } = false;
/// <summary>
/// Multicrew or not.
/// </summary>
public bool Multicrew { get; set; } = false;
/// <summary>
/// Player's ship ID
/// </summary>
public ulong? ID { get; set; }
/// <summary>
/// Star system
/// </summary>
public string? StarSystem { get; set; }
/// <summary>
/// System address
/// </summary>
public ulong? SystemAddress { get; set; }
/// <summary>
/// Body, name e.g. HIP 6182 B 2 a
/// </summary>
public string? Body { get; set; }
/// <summary>
/// Body ID
/// </summary>
public ulong? BodyID { get; set; }
/// <summary>
/// Disembarked on a station?
/// </summary>
public bool OnStation { get; set; } = false;
/// <summary>
/// Disembarked on a planet?
/// </summary>
public bool OnPlanet { get; set; } = false;
protected override void Initialise() {
SRV = JSON.Value<bool?>("SRV") ?? false;
Taxi = JSON.Value<bool?>("Taxi") ?? false;
Multicrew = JSON.Value<bool?>("Multicrew") ?? false;
ID = JSON.Value<ulong?>("ID");
StarSystem = JSON.Value<string?>("StarSystem");
SystemAddress = JSON.Value<ulong?>("SystemAddress");
Body = JSON.Value<string?>("Body");
BodyID = JSON.Value<ulong?>("BodyID");
OnStation = JSON.Value<bool?>("OnStation") ?? false;
OnPlanet = JSON.Value<bool?>("OnPlanet") ?? false;
}
}

View File

@@ -2,15 +2,36 @@
namespace EDPlayerJournal.Entries; namespace EDPlayerJournal.Entries;
public class DockedEntry : Entry { public class DockedEntry : Entry {
/// <summary>
/// Name of the station
/// </summary>
public string? StationName { get; set; } public string? StationName { get; set; }
/// <summary>
/// Star system of the station
/// </summary>
public string? StarSystem { get; set; } public string? StarSystem { get; set; }
/// <summary>
/// System address
/// </summary>
public ulong? SystemAddress { get; set; } public ulong? SystemAddress { get; set; }
/// <summary>
/// Faction owning the station.
/// </summary>
public string? StationFaction { get; set; } public string? StationFaction { get; set; }
/// <summary>
/// State of the station, new in Update 14
/// </summary>
public string? StationState { get; set; }
protected override void Initialise() { protected override void Initialise() {
StationName = JSON.Value<string>("StationName"); StationName = JSON.Value<string>("StationName");
StarSystem = JSON.Value<string>("StarSystem"); StarSystem = JSON.Value<string>("StarSystem");
SystemAddress = JSON.Value<ulong?>("SystemAddress"); SystemAddress = JSON.Value<ulong?>("SystemAddress");
StationState = JSON.Value<string>("StationState");
JObject? faction = JSON.Value<JObject>("StationFaction"); JObject? faction = JSON.Value<JObject>("StationFaction");
if (faction != null) { if (faction != null) {
StationFaction = faction.Value<string>("Name") ?? ""; StationFaction = faction.Value<string>("Name") ?? "";

View File

@@ -0,0 +1,42 @@
namespace EDPlayerJournal.Entries;
public class DropshipDeployEntry : Entry {
/// <summary>
/// Star system this drop happened in.
/// </summary>
public string? StarSystem { get; set; }
/// <summary>
/// System address
/// </summary>
public ulong? SystemAddress { get; set; }
/// <summary>
/// Planetary body
/// </summary>
public string? Body { get; set; }
/// <summary>
/// Body ID
/// </summary>
public ulong? BodyID { get; set; }
/// <summary>
/// Whether it happened on station.
/// </summary>
public bool? OnStation { get; set; }
/// <summary>
/// Whether it happened on a planet.
/// </summary>
public bool? OnPlanet { get; set; }
protected override void Initialise() {
StarSystem = JSON.Value<string?>("StarSystem");
SystemAddress = JSON.Value<ulong?>("SystemAddress");
Body = JSON.Value<string?>("Body");
BodyID = JSON.Value<ulong?>("BodyID");
OnStation = JSON.Value<bool?>("OnStation");
OnPlanet = JSON.Value<bool?>("OnPlanet");
}
}

View File

@@ -0,0 +1,66 @@
namespace EDPlayerJournal.Entries;
public class EmbarkEntry : Entry {
/// <summary>
/// Disembarked into an SRV?
/// </summary>
public bool SRV { get; set; } = false;
/// <summary>
/// Taxi?
/// </summary>
public bool Taxi { get; set; } = false;
/// <summary>
/// Multicrew or not.
/// </summary>
public bool Multicrew { get; set; } = false;
/// <summary>
/// Player's ship ID
/// </summary>
public ulong? ID { get; set; }
/// <summary>
/// Star system
/// </summary>
public string? StarSystem { get; set; }
/// <summary>
/// System address
/// </summary>
public ulong? SystemAddress { get; set; }
/// <summary>
/// Body, name e.g. HIP 6182 B 2 a
/// </summary>
public string? Body { get; set; }
/// <summary>
/// Body ID
/// </summary>
public ulong? BodyID { get; set; }
/// <summary>
/// Disembarked on a station?
/// </summary>
public bool OnStation { get; set; } = false;
/// <summary>
/// Disembarked on a planet?
/// </summary>
public bool OnPlanet { get; set; } = false;
protected override void Initialise() {
SRV = JSON.Value<bool?>("SRV") ?? false;
Taxi = JSON.Value<bool?>("Taxi") ?? false;
Multicrew = JSON.Value<bool?>("Multicrew") ?? false;
ID = JSON.Value<ulong?>("ID");
StarSystem = JSON.Value<string?>("StarSystem");
SystemAddress = JSON.Value<ulong?>("SystemAddress");
Body = JSON.Value<string?>("Body");
BodyID = JSON.Value<ulong?>("BodyID");
OnStation = JSON.Value<bool?>("OnStation") ?? false;
OnPlanet = JSON.Value<bool?>("OnPlanet") ?? false;
}
}

View File

@@ -12,13 +12,18 @@ namespace EDPlayerJournal.Entries;
/// </summary> /// </summary>
public class Entry { public class Entry {
private static readonly Dictionary<string, Type> classes = new Dictionary<string, Type> { private static readonly Dictionary<string, Type> classes = new Dictionary<string, Type> {
{ Events.ApproachSettlement, typeof(ApproachSettlementEntry) },
{ Events.Bounty, typeof(BountyEntry) }, { Events.Bounty, typeof(BountyEntry) },
{ Events.CapShipBond, typeof(CapShipBondEntry) },
{ Events.Commander, typeof(CommanderEntry) }, { Events.Commander, typeof(CommanderEntry) },
{ Events.CommitCrime, typeof(CommitCrimeEntry) }, { Events.CommitCrime, typeof(CommitCrimeEntry) },
{ Events.Died, typeof(DiedEntry) }, { Events.Died, typeof(DiedEntry) },
{ Events.Disembark, typeof(DisembarkEntry) },
{ Events.Docked, typeof(DockedEntry) }, { Events.Docked, typeof(DockedEntry) },
{ Events.FileHeader, typeof(FileHeaderEntry) }, { Events.DropshipDeploy, typeof(DropshipDeployEntry) },
{ Events.Embark, typeof(EmbarkEntry) },
{ Events.FactionKillBond, typeof(FactionKillBondEntry) }, { Events.FactionKillBond, typeof(FactionKillBondEntry) },
{ Events.FileHeader, typeof(FileHeaderEntry) },
{ Events.FSDJump, typeof(FSDJumpEntry) }, { Events.FSDJump, typeof(FSDJumpEntry) },
{ Events.HullDamage, typeof(HullDamageEntry) }, { Events.HullDamage, typeof(HullDamageEntry) },
{ Events.LoadGame, typeof(LoadGameEntry) }, { Events.LoadGame, typeof(LoadGameEntry) },
@@ -32,6 +37,7 @@ public class Entry {
{ Events.MissionRedirected, typeof(MissionRedirectedEntry) }, { Events.MissionRedirected, typeof(MissionRedirectedEntry) },
{ Events.Missions, typeof(MissionsEntry) }, { Events.Missions, typeof(MissionsEntry) },
{ Events.MultiSellExplorationData, typeof(MultiSellExplorationDataEntry) }, { Events.MultiSellExplorationData, typeof(MultiSellExplorationDataEntry) },
{ Events.ReceiveText, typeof(ReceiveTextEntry) },
{ Events.RedeemVoucher, typeof(RedeemVoucherEntry) }, { Events.RedeemVoucher, typeof(RedeemVoucherEntry) },
{ Events.SearchAndRescue, typeof(SearchAndRescueEntry) }, { Events.SearchAndRescue, typeof(SearchAndRescueEntry) },
{ Events.SellExplorationData, typeof(SellExplorationDataEntry) }, { Events.SellExplorationData, typeof(SellExplorationDataEntry) },
@@ -39,6 +45,9 @@ public class Entry {
{ Events.SellOrganicData, typeof(SellOrganicDataEntry) }, { Events.SellOrganicData, typeof(SellOrganicDataEntry) },
{ Events.ShieldState, typeof(ShieldStateEntry) }, { Events.ShieldState, typeof(ShieldStateEntry) },
{ Events.ShipTargeted, typeof(ShipTargetedEntry) }, { Events.ShipTargeted, typeof(ShipTargetedEntry) },
{ Events.SupercruiseDestinationDrop, typeof(SupercruiseDestinationDropEntry) },
{ Events.SupercruiseEntry, typeof(SupercruiseEntryEntry) },
{ Events.SupercruiseExit, typeof(SupercruiseExitEntry) },
{ Events.UnderAttack, typeof(UnderAttackEntry) }, { Events.UnderAttack, typeof(UnderAttackEntry) },
}; };
@@ -98,7 +107,7 @@ public class Entry {
this.datetime = json.GetValue("timestamp")?.ToString(); this.datetime = json.GetValue("timestamp")?.ToString();
if (!string.IsNullOrEmpty(this.datetime)) { if (!string.IsNullOrEmpty(this.datetime)) {
this.timestamp = DateTime.Parse(this.datetime); this.timestamp = DateTime.Parse(this.datetime, null, System.Globalization.DateTimeStyles.RoundtripKind);
} }
} }

View File

@@ -1,11 +1,16 @@
namespace EDPlayerJournal.Entries; namespace EDPlayerJournal.Entries;
public class Events { public class Events {
public static readonly string ApproachSettlement = "ApproachSettlement";
public static readonly string Bounty = "Bounty"; public static readonly string Bounty = "Bounty";
public static readonly string CapShipBond = "CapShipBond";
public static readonly string Commander = "Commander"; public static readonly string Commander = "Commander";
public static readonly string CommitCrime = "CommitCrime"; public static readonly string CommitCrime = "CommitCrime";
public static readonly string Died = "Died"; public static readonly string Died = "Died";
public static readonly string Disembark = "Disembark";
public static readonly string Docked = "Docked"; public static readonly string Docked = "Docked";
public static readonly string DropshipDeploy = "DropshipDeploy";
public static readonly string Embark = "Embark";
public static readonly string FactionKillBond = "FactionKillBond"; public static readonly string FactionKillBond = "FactionKillBond";
public static readonly string FighterDestroyed = "FighterDestroyed"; public static readonly string FighterDestroyed = "FighterDestroyed";
public static readonly string FileHeader = "Fileheader"; public static readonly string FileHeader = "Fileheader";
@@ -22,6 +27,7 @@ public class Events {
public static readonly string MissionRedirected = "MissionRedirected"; public static readonly string MissionRedirected = "MissionRedirected";
public static readonly string Missions = "Missions"; public static readonly string Missions = "Missions";
public static readonly string MultiSellExplorationData = "MultiSellExplorationData"; public static readonly string MultiSellExplorationData = "MultiSellExplorationData";
public static readonly string ReceiveText = "ReceiveText";
public static readonly string RedeemVoucher = "RedeemVoucher"; public static readonly string RedeemVoucher = "RedeemVoucher";
public static readonly string SearchAndRescue = "SearchAndRescue"; public static readonly string SearchAndRescue = "SearchAndRescue";
public static readonly string SellExplorationData = "SellExplorationData"; public static readonly string SellExplorationData = "SellExplorationData";
@@ -29,5 +35,9 @@ public class Events {
public static readonly string SellOrganicData = "SellOrganicData"; public static readonly string SellOrganicData = "SellOrganicData";
public static readonly string ShieldState = "ShieldState"; public static readonly string ShieldState = "ShieldState";
public static readonly string ShipTargeted = "ShipTargeted"; public static readonly string ShipTargeted = "ShipTargeted";
public static readonly string Shutdown = "Shutdown";
public static readonly string SupercruiseDestinationDrop = "SupercruiseDestinationDrop";
public static readonly string SupercruiseEntry = "SupercruiseEntry";
public static readonly string SupercruiseExit = "SupercruiseExit";
public static readonly string UnderAttack = "UnderAttack"; public static readonly string UnderAttack = "UnderAttack";
} }

View File

@@ -26,6 +26,22 @@ public class FileHeaderEntry : Entry {
/// </summary> /// </summary>
public string? Build { get; set; } public string? Build { get; set; }
/// <summary>
/// Returns true if the version is legacy (3.X)
/// <summary>
public bool IsLegacy {
get {
return GameVersion.StartsWith("3.");
}
}
/// <summary>
/// Returns true if the version is live (4.x)
/// </summary>
public bool IsLive {
get { return !IsLegacy; }
}
protected override void Initialise() { protected override void Initialise() {
Part = JSON.Value<ulong?>("part") ?? 1; Part = JSON.Value<ulong?>("part") ?? 1;
Language = JSON.Value<string?>("language") ?? string.Empty; Language = JSON.Value<string?>("language") ?? string.Empty;

View File

@@ -1,18 +1,47 @@
using System; using Newtonsoft.Json.Linq;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json.Linq;
namespace EDPlayerJournal.Entries; namespace EDPlayerJournal.Entries;
public class LocationEntry : Entry { public class LocationEntry : Entry {
/// <summary>
/// Current star system.
/// </summary>
public string? StarSystem { get; set; } public string? StarSystem { get; set; }
/// <summary>
/// Faction in control of the current star system. Empty for unpopulated
/// systems.
/// </summary>
public string? SystemFaction { get; set; } public string? SystemFaction { get; set; }
public string? StationName { get; set; }
/// <summary>
/// 64bit system address.
/// </summary>
public ulong SystemAddress { get; set; } public ulong SystemAddress { get; set; }
/// <summary>
/// Station name if docked at a station.
/// </summary>
public string? StationName { get; set; }
/// <summary>
/// Faction in control of the current station, if docked.
/// </summary>
public string? StationFaction { get; set; }
/// <summary>
/// Body within the system, might be null.
/// </summary>
public string? Body { get; set; } public string? Body { get; set; }
/// <summary>
/// Returns true if the player is docked somewhere.
/// </summary>
public bool Docked { get; set; } public bool Docked { get; set; }
/// <summary>
/// Position of the star system.
/// </summary>
public long[]? StarPos { get; set; } public long[]? StarPos { get; set; }
public List<Faction> SystemFactions { get; set; } = new List<Faction>(); public List<Faction> SystemFactions { get; set; } = new List<Faction>();
@@ -30,7 +59,12 @@ public class LocationEntry : Entry {
JObject? systemfaction = JSON.Value<JObject>("SystemFaction"); JObject? systemfaction = JSON.Value<JObject>("SystemFaction");
if (systemfaction != null) { if (systemfaction != null) {
SystemFaction = systemfaction.Value<string>("Name") ?? ""; SystemFaction = systemfaction.Value<string>("Name");
}
JObject? stationfaction = JSON.Value<JObject>("StationFaction");
if (stationfaction != null) {
StationFaction = stationfaction.Value<string>("Name");
} }
JArray? factions = JSON.Value<JArray>("Factions"); JArray? factions = JSON.Value<JArray>("Factions");

View File

@@ -0,0 +1,77 @@
namespace EDPlayerJournal.Entries;
public class ReceiveTextEntry : Entry {
/// <summary>
/// From whom this message is
/// </summary>
public string? From { get; set; }
/// <summary>
/// The message, if it is just an NPC text, it will be a message ID
/// </summary>
public string? Message { get; set; }
/// <summary>
/// Message localised
/// </summary>
public string? MessageLocalised { get; set; }
/// <summary>
/// On what channel this was received.
/// </summary>
public string? Channel { get; set; }
public bool HasNPCCategory {
get {
if (From == null) {
return false;
}
if (From.Contains(';') && From.StartsWith("$")) {
return true;
}
return false;
}
}
/// <summary>
/// Returns the NPC's category, if it has one.
/// </summary>
public string? NPCCategory {
get {
if (!HasNPCCategory || From == null) {
return null;
}
string[] parts = From.Split(";", StringSplitOptions.TrimEntries);
if (parts.Length < 2) {
return null;
}
return string.Format("{0};", parts[0]);
}
}
/// <summary>
/// Returns the NPC's category, if it has one.
/// </summary>
public string? NPCName {
get {
if (!HasNPCCategory || From == null) {
return null;
}
string[] parts = From.Split(";", StringSplitOptions.TrimEntries);
if (parts.Length < 2) {
return null;
}
return parts[1];
}
}
protected override void Initialise() {
From = JSON.Value<string>("From");
Message = JSON.Value<string>("Message");
MessageLocalised = JSON.Value<string>("Message_localised");
Channel = JSON.Value<string>("Channel");
}
}

View File

@@ -4,7 +4,7 @@ using System.Collections.Generic;
namespace EDPlayerJournal.Entries; namespace EDPlayerJournal.Entries;
public class RedeemVoucherEntry : Entry { public class RedeemVoucherEntry : Entry {
protected override void Initialise() { protected override void Initialise() {
Amount = (JSON.Value<int?>("Amount") ?? 0); Amount = (JSON.Value<ulong?>("Amount") ?? 0);
Type = JSON.Value<string>("Type"); Type = JSON.Value<string>("Type");
/* according to API, there should be a Factions structure */ /* according to API, there should be a Factions structure */
@@ -12,7 +12,7 @@ public class RedeemVoucherEntry : Entry {
if (factions != null) { if (factions != null) {
foreach (JObject faction in factions.Children<JObject>()) { foreach (JObject faction in factions.Children<JObject>()) {
string? faction_name = faction.Value<string>("Faction"); string? faction_name = faction.Value<string>("Faction");
long? faction_bounty = faction.Value<long?>("Amount"); ulong? faction_bounty = faction.Value<ulong?>("Amount");
if (faction_name == null || faction_name.Length <= 0 || faction_bounty == null) { if (faction_name == null || faction_name.Length <= 0 || faction_bounty == null) {
continue; continue;
} }
@@ -35,8 +35,29 @@ public class RedeemVoucherEntry : Entry {
} }
} }
public int Amount { get; set; } = 0; public ulong GetBountyForFaction(string? a) {
if (a == null) {
return 0;
}
var relevant = FactionBounties.Where(x => EDPlayerJournal.Factions.CompareFactions(x.Key, a) == 0).ToList();
if (relevant == null || relevant.Count() == 0) {
return 0;
}
return (ulong)relevant.Sum(x => (decimal)x.Value);
}
public ulong Amount { get; set; } = 0;
public string? Type { get; set; } = "Bounty"; public string? Type { get; set; } = "Bounty";
public List<string> Factions { get; set; } = new List<string>();
public Dictionary<string, long> FactionBounties { get; set; } = new Dictionary<string, long>(); /// <summary>
/// List of factions
/// </summary>
public List<string> Factions { get; set; } = new();
/// <summary>
/// Bounties awarded by faction
/// </summary>
public Dictionary<string, ulong> FactionBounties { get; set; } = new();
} }

View File

@@ -8,11 +8,17 @@ public class BioData {
public string? SpeciesLocalised { get; set; } public string? SpeciesLocalised { get; set; }
public long Value { get; set; } = 0; public long Value { get; set; } = 0;
public long Bonus { get; set; } = 0; public long Bonus { get; set; } = 0;
public long TotalValue => Value + Bonus;
public long TotalValue {
get {
return Value + Bonus;
}
}
} }
public class SellOrganicDataEntry : Entry { public class SellOrganicDataEntry : Entry {
public long MarketID { get; set; } public long MarketID { get; set; }
public List<BioData> BioData => new List<BioData>();
public List<BioData> BioData { get; } = new();
protected override void Initialise() { protected override void Initialise() {
MarketID = JSON.Value<long?>("MarketID") ?? 0; MarketID = JSON.Value<long?>("MarketID") ?? 0;

View File

@@ -0,0 +1,30 @@
namespace EDPlayerJournal.Entries;
public class SupercruiseDestinationDropEntry : Entry {
/// <summary>
/// Destination type, internal string.
/// </summary>
public string? Type { get; set; } = null;
/// <summary>
/// Destination type, localised string.
/// </summary>
public string? TypeLocalised { get; set; } = null;
/// <summary>
/// Threat of the destination, if known.
/// </summary>
public long? Threat { get; set; } = null;
/// <summary>
/// Market ID if it has one.
/// </summary>
public ulong? MarketID { get; set; } = null;
protected override void Initialise() {
Type = JSON.Value<string>("Type");
TypeLocalised = JSON.Value<string>("Type_Localised");
Threat = JSON.Value<long?>("Threat");
MarketID = JSON.Value<ulong?>("MarketID");
}
}

View File

@@ -0,0 +1,30 @@
namespace EDPlayerJournal.Entries;
public class SupercruiseEntryEntry : Entry {
/// <summary>
/// Taxi?
/// </summary>
public bool Taxi { get; set; } = false;
/// <summary>
/// Multicrew or not.
/// </summary>
public bool Multicrew { get; set; } = false;
/// <summary>
/// Star system
/// </summary>
public string? StarSystem { get; set; }
/// <summary>
/// System address
/// </summary>
public ulong? SystemAddress { get; set; }
protected override void Initialise() {
Taxi = JSON.Value<bool?>("Taxi") ?? false;
Multicrew = JSON.Value<bool?>("Multicrew") ?? false;
StarSystem = JSON.Value<string?>("StarSystem");
SystemAddress = JSON.Value<ulong?>("SystemAddress");
}
}

View File

@@ -0,0 +1,48 @@
namespace EDPlayerJournal.Entries;
public class SupercruiseExitEntry : Entry {
/// <summary>
/// Taxi?
/// </summary>
public bool Taxi { get; set; } = false;
/// <summary>
/// Multicrew or not.
/// </summary>
public bool Multicrew { get; set; } = false;
/// <summary>
/// Star system
/// </summary>
public string? StarSystem { get; set; }
/// <summary>
/// System address
/// </summary>
public ulong? SystemAddress { get; set; }
/// <summary>
/// Body, name e.g. HIP 6182 B 2 a
/// </summary>
public string? Body { get; set; }
/// <summary>
/// Body ID
/// </summary>
public ulong? BodyID { get; set; }
/// <summary>
/// Body type (star, planet etc.)
/// </summary>
public string? BodyType { get; set; }
protected override void Initialise() {
Taxi = JSON.Value<bool?>("Taxi") ?? false;
Multicrew = JSON.Value<bool?>("Multicrew") ?? false;
StarSystem = JSON.Value<string?>("StarSystem");
SystemAddress = JSON.Value<ulong?>("SystemAddress");
Body = JSON.Value<string?>("Body");
BodyType = JSON.Value<string?>("BodyType");
BodyID = JSON.Value<ulong?>("BodyID");
}
}

View File

@@ -33,12 +33,83 @@ public class Factions {
/// <summary> /// <summary>
/// Internal name for the Pilots Federation faction /// Internal name for the Pilots Federation faction
/// </summary> /// </summary>
public static string PilotsFederation = "$faction_PilotsFederation;"; public static string PilotsFederationInternal = "$faction_PilotsFederation;";
/// <summary>
/// Name used for Pilots Federation in vouchers
/// </summary>
public static string PilotsFederationVouchers = "PilotsFederation";
/// <summary>
/// Friendly name of the Pilots Federation
/// </summary>
public static string PilotsFederation = "Pilots' Federation";
/// <summary> /// <summary>
/// Internal name for the Thargoid faction /// Internal name for the Thargoid faction
/// </summary> /// </summary>
public static string Thargoid = "$faction_Thargoid;"; public static string ThargoidInternal = "$faction_Thargoid;";
/// <summary>
/// Localised name of the Thargoids
/// </summary>
public static string Thargoid = "Thargoids";
/// <summary>
/// The faction the fleet carriers use.
/// </summary>
public static string FleetCarrier = "FleetCarrier";
public static bool IsPilotsFederation(string? name) {
if (name == null) {
return false;
}
if (string.Compare(name, PilotsFederationInternal) == 0 ||
string.Compare(name, PilotsFederationVouchers) == 0 ||
string.Compare(name, PilotsFederation) == 0) {
return true;
}
return false;
}
public static bool IsThargoid(string? name) {
if (name == null) {
return false;
}
if (string.Compare(name, ThargoidInternal) == 0 ||
string.Compare(name, Thargoid) == 0) {
return true;
}
return false;
}
/// <summary>
/// Compares two factions names and sees if they are the same faction. Since
/// factions can have an internal name, and a public name, this function takes
/// these into account.
/// </summary>
/// <param name="a"></param>
/// <param name="b"></param>
/// <returns></returns>
public static int CompareFactions(string? a, string? b) {
if (a == null || b == null) {
return -1;
}
if (IsPilotsFederation(a) && IsPilotsFederation(b)) {
return 0;
}
if (IsThargoid(a) && IsThargoid(b)) {
return 0;
}
return string.Compare(a, b);
}
} }
public class Faction { public class Faction {

View File

@@ -0,0 +1,73 @@
namespace EDPlayerJournal;
/// <summary>
/// Contains information regarding instances you can supercruise into,
/// such as combat zones, installations and megaship scenarios.
/// </summary>
public class Instances {
/// <summary>
/// Low ship combat zone
/// </summary>
public static readonly string WarzoneLow = "$Warzone_PointRace_Low";
/// <summary>
/// Medium ship combat zone
/// </summary>
public static readonly string WarzoneMedium = "$Warzone_PointRace_Med";
/// <summary>
/// High ship combat zone.
/// </summary>
public static readonly string WarzoneHigh = "$Warzone_PointRace_High";
/// <summary>
/// Low Thargoid combat zone
/// </summary>
public static readonly string WarzoneThargoidLow = "$Warzone_TG_Low";
/// <summary>
/// Medium Thargoid combat zone
/// </summary>
public static readonly string WarzoneThargoidMedium = "$Warzone_TG_Med";
/// <summary>
/// High Thargoid combat zone
/// </summary>
public static readonly string WarzoneThargoidHigh = "$Warzone_TG_High";
/// <summary>
/// Very High Thargoid combat zone
/// </summary>
public static readonly string WarzoneThargoidVeryHigh = "$Warzone_TG_VeryHigh";
public static bool IsThargoidWarzone(string type) {
return
IsInstance(type, WarzoneThargoidLow) ||
IsInstance(type, WarzoneThargoidMedium) ||
IsInstance(type, WarzoneThargoidHigh) ||
IsInstance(type, WarzoneThargoidVeryHigh)
;
}
public static bool IsHumanWarzone(string type) {
return
IsInstance(type, WarzoneLow) ||
IsInstance(type, WarzoneMedium) ||
IsInstance(type, WarzoneHigh)
;
}
public static bool IsWarzone(string type) {
return IsHumanWarzone(type) || IsThargoidWarzone(type);
}
public static bool IsInstance(string type, string instance) {
if (string.IsNullOrEmpty(type) || string.IsNullOrEmpty(instance)) {
return false;
}
// Instance names are split by a semi colon, with the remainder being
// additional info to such as index.
string[] parts = type.Split(":");
if (!parts[0].StartsWith("$")) {
return false;
}
return string.Compare(parts[0], instance, true) == 0;
}
}

View File

@@ -36,6 +36,31 @@ public class MissionInfluence {
/// </summary> /// </summary>
public string Influence { get; set; } = string.Empty; public string Influence { get; set; } = string.Empty;
public long InfluenceAmount {
get {
string trend = TrendAdjustedInfluence;
return (long)
(trend.Count(x => x == '-') * -1) +
trend.Count(x => x == '+')
;
}
}
/// <summary>
/// Returns how much influence was made, represented in pluses for positive influence,
/// and minuses with negative influence. This takes Trend (up, bad etc.) into account.
/// </summary>
public string TrendAdjustedInfluence {
get {
if (!string.IsNullOrEmpty(Trend) &&
Trend.Contains("bad", StringComparison.OrdinalIgnoreCase)) {
return new string('-', Influence.Length);
} else {
return new string('+', Influence.Length);
}
}
}
public static MissionInfluence FromJSON(JObject obj) { public static MissionInfluence FromJSON(JObject obj) {
MissionInfluence missionInfluence = new MissionInfluence(); MissionInfluence missionInfluence = new MissionInfluence();
@@ -109,6 +134,11 @@ public class MissionFactionEffects {
} }
public class Mission : IComparable<Mission> { public class Mission : IComparable<Mission> {
/// <summary>
/// Passenger type for refugees
/// </summary>
public static readonly string PassengerTypeRefugee = "Refugee";
public ulong MissionID { get; set; } = 0; public ulong MissionID { get; set; } = 0;
/// <summary> /// <summary>
@@ -250,6 +280,43 @@ public class Mission : IComparable<Mission> {
/// </summary> /// </summary>
public List<MissionFactionEffects> FactionEffects { get; set; } = new List<MissionFactionEffects>(); public List<MissionFactionEffects> FactionEffects { get; set; } = new List<MissionFactionEffects>();
/// <summary>
/// Returns true if the name is an on foot mission.
/// </summary>
public bool IsOnFoot {
get {
if (string.IsNullOrEmpty(Name)) {
return false;
}
return Name.Contains("OnFoot");
}
}
public bool IsPassengerMission {
get {
if (PassengerCount == null || PassengerCount == 0) {
return false;
}
return true;
}
}
public bool IsRescueMission {
get {
if (!IsPassengerMission) {
return false;
}
if (string.Compare(PassengerType, PassengerTypeRefugee) == 0) {
return true;
}
return false;
}
}
/// <summary> /// <summary>
/// Returns a friendly human-readable name for the mission. If a localised name is available /// Returns a friendly human-readable name for the mission. If a localised name is available
/// it will use that, baring that it will check EnglishMissionNames for a translation, and /// it will use that, baring that it will check EnglishMissionNames for a translation, and
@@ -352,27 +419,29 @@ public class Mission : IComparable<Mission> {
/// <param name="faction">Faction name in question.</param> /// <param name="faction">Faction name in question.</param>
/// <param name="systemaddr">Star System address</param> /// <param name="systemaddr">Star System address</param>
/// <returns>null if no entry was found, or a string denoting pluses for the amount influence gained.</returns> /// <returns>null if no entry was found, or a string denoting pluses for the amount influence gained.</returns>
public string? GetInfluenceForFaction(string faction, ulong systemaddr) { public MissionInfluence[]? GetInfluenceForFaction(string faction, ulong systemaddr) {
var results = FactionEffects var results = FactionEffects
.Where(x => string.Compare(x.Faction, faction) == 0) .Where(x => string.Compare(x.Faction, faction) == 0)
.SelectMany(x => x.Influences) .SelectMany(x => x.Influences)
.Where(x => (x.SystemAddress != null && x.SystemAddress == systemaddr)) .Where(x => (x.SystemAddress != null && x.SystemAddress == systemaddr))
.Select(x => x.Influence) .Select(x => x)
.ToArray() .ToArray()
; ;
if (results == null || results.Length == 0) { if (results == null || results.Length == 0) {
return null; return new MissionInfluence[0];
} }
return string.Join("", results); return results;
} }
/// <summary> /// <summary>
/// A convenient Dictionary containing all influences given out by faction, /// A convenient Dictionary containing all influences given out by faction,
/// then by system address and then by influence handed out. /// then by system address and then by influence handed out. Influence can
/// be either a series of "+" for positive influence, or "-" for negative
/// influence.
/// </summary> /// </summary>
public Dictionary<string, Dictionary<ulong, string>> Influences { public Dictionary<string, Dictionary<ulong, MissionInfluence>> Influences {
get { get {
return FactionEffects return FactionEffects
.Where(x => x.Faction != null) .Where(x => x.Faction != null)
@@ -380,7 +449,10 @@ public class Mission : IComparable<Mission> {
x => (x.Faction ?? string.Empty), x => (x.Faction ?? string.Empty),
x => x.Influences x => x.Influences
.Where(x => x.SystemAddress != null) .Where(x => x.SystemAddress != null)
.ToDictionary(x => (x.SystemAddress ?? 0), x => x.Influence) .ToDictionary(
x => (x.SystemAddress ?? 0),
x => x
)
); );
} }
} }

122
EDPlayerJournal/NPC.cs Normal file
View File

@@ -0,0 +1,122 @@
namespace EDPlayerJournal;
public class NPCs {
/// <summary>
/// Internal name of Spec Ops Wing Alpha
/// </summary>
public static string SpecOpsAInternal = "$LUASC_Scenario_Warzone_NPC_SpecOps_A;";
/// <summary>
/// Internal name of Spec Ops Wing Beta
/// </summary>
public static string SpecOpsBInternal = "$LUASC_Scenario_Warzone_NPC_SpecOps_B;";
/// <summary>
/// Internal name of Spec Ops Wing Gamma
/// </summary>
public static string SpecOpsGInternal = "$LUASC_Scenario_Warzone_NPC_SpecOps_G;";
/// <summary>
/// Internal name of Spec Ops Wing Delta
/// </summary>
public static string SpecOpsDInternal = "$LUASC_Scenario_Warzone_NPC_SpecOps_D;";
/// <summary>
/// Empire captain
/// </summary>
public static string EmpireCaptain = "$LUASC_Scenario_Warzone_NPC_WarzoneGeneral_Emp;";
/// <summary>
/// Federation captain
/// </summary>
public static string FederationCaptain = "$LUASC_Scenario_Warzone_NPC_WarzoneGeneral_Fed;";
/// <summary>
/// Federation captain
/// </summary>
public static string IndependentCaptain = "$LUASC_Scenario_Warzone_NPC_WarzoneGeneral_Ind;";
/// <summary>
/// Warzone correspondant
/// </summary>
public static string WarzoneCorrespondent = "$LUASC_Scenario_Warzone_NPC_WarzoneCorrespondent;";
/// <summary>
/// AX Military NPC
/// </summary>
public static string AXMilitary = "$Name_AX_Military;";
/// <summary>
/// Returns true if the pilotname is either a captain, specops, or correspondent
/// </summary>
/// <param name="pilotname"></param>
/// <returns></returns>
public static bool IsWarzoneNPC(string? pilotname) {
if (IsWarzoneCaptain(pilotname) ||
IsWarzoneCorrespondent(pilotname) ||
IsSpecOps(pilotname)) {
return true;
}
return false;
}
/// <summary>
/// Returns true if the given pilot name is a warzone correspondent
/// </summary>
/// <param name="name"></param>
/// <returns></returns>
public static bool IsWarzoneCorrespondent(string? pilotname) {
if (pilotname == null) {
return false;
}
if (string.Compare(pilotname, WarzoneCorrespondent) == 0) {
return true;
}
return false;
}
/// <summary>
/// Returns true if the given pilot name is a spec ops wing.
/// </summary>
/// <param name="name"></param>
/// <returns></returns>
public static bool IsSpecOps(string? pilotname) {
if (pilotname == null) {
return false;
}
if (string.Compare(pilotname, SpecOpsAInternal) == 0 ||
string.Compare(pilotname, SpecOpsBInternal) == 0 ||
string.Compare(pilotname, SpecOpsGInternal) == 0 ||
string.Compare(pilotname, SpecOpsDInternal) == 0) {
return true;
}
return false;
}
/// <summary>
/// Returns true if the given pilot name is a warzone captain
/// </summary>
/// <param name="name"></param>
/// <returns></returns>
public static bool IsWarzoneCaptain(string? pilotname) {
if (pilotname == null) {
return false;
}
if (string.Compare(pilotname, EmpireCaptain) == 0 ||
string.Compare(pilotname, FederationCaptain) == 0 ||
string.Compare(pilotname, IndependentCaptain) == 0) {
return true;
}
return false;
}
}
public class NPC {
}

View File

@@ -1,43 +1,61 @@
using System; namespace EDPlayerJournal;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace EDPlayerJournal;
public enum ThargoidVessel { public enum ThargoidVessel {
Unknown = 0, Unknown = 0,
Scout = 1, Scout = 1,
/// <summary>
/// According to AX wiki no longer found ingame
/// </summary>
Orthrus = 2, Orthrus = 2,
Cyclops = 3, Cyclops = 3,
Basilisk = 4, Basilisk = 4,
Medusa = 5, Medusa = 5,
Hydra = 6, Hydra = 6,
// Includes Glaive and Scythe
Hunter = 7,
/// <summary>
/// Thargoid drone
/// </summary>
Revenant = 8,
} }
public class Thargoid { public class Thargoid {
public static string ThargoidFaction = Factions.Thargoid; public static string ThargoidFaction = Factions.Thargoid;
public static Dictionary<ulong, ThargoidVessel> VesselPayout { get; } = new() { public static Dictionary<ulong, ThargoidVessel> VesselPayout { get; } = new() {
{ 80000, ThargoidVessel.Scout }, // Up to date values ever since 14.02
{ 8000000, ThargoidVessel.Cyclops }, { 25000, ThargoidVessel.Revenant },
{ 24000000, ThargoidVessel.Basilisk }, { 65000, ThargoidVessel.Scout },
{ 40000000, ThargoidVessel.Medusa }, { 75000, ThargoidVessel.Scout },
{ 60000000, ThargoidVessel.Hydra }, // New in Update 15
{ 4500000, ThargoidVessel.Hunter },
{ 6500000, ThargoidVessel.Cyclops },
{ 20000000, ThargoidVessel.Basilisk },
//{ 25000000, ThargoidVessel.Orthrus },
{ 34000000, ThargoidVessel.Medusa },
// March, 16th 2023 the Orthrus payout was buffed again.
{ 40000000, ThargoidVessel.Orthrus },
{ 50000000, ThargoidVessel.Hydra },
// These are the old values pre Update 14.02
//{ 80000, ThargoidVessel.Scout },
//{ 8000000, ThargoidVessel.Cyclops },
//{ 24000000, ThargoidVessel.Basilisk },
// In Update 14.1 the payout for Orthrus has been rebalanced.
//{ 30000000, ThargoidVessel.Orthrus },
//{ 40000000, ThargoidVessel.Medusa },
// This used to be the old payout value for Orthrus, it now conflicts
// with Post Update 14.02 Hydra values
//{ 50000000, ThargoidVessel.Orthrus },
//{ 60000000, ThargoidVessel.Hydra },
}; };
public static Dictionary<ThargoidVessel, string?> VesselNames { get; } = new() { public static Dictionary<ThargoidVessel, string?> VesselNames { get; } = new() {
{ ThargoidVessel.Unknown, null }, { ThargoidVessel.Unknown, null },
{ ThargoidVessel.Scout, "Thargoid Scout" }, { ThargoidVessel.Revenant, "Revenant" },
{ ThargoidVessel.Scout, "Scout" },
{ ThargoidVessel.Orthrus, "Orthrus" }, { ThargoidVessel.Orthrus, "Orthrus" },
{ ThargoidVessel.Cyclops, "Cyclops" }, { ThargoidVessel.Cyclops, "Cyclops" },
{ ThargoidVessel.Basilisk, "Basilisk" }, { ThargoidVessel.Basilisk, "Basilisk" },
{ ThargoidVessel.Medusa, "Medusa" }, { ThargoidVessel.Medusa, "Medusa" },
{ ThargoidVessel.Hydra, "Hydra" }, { ThargoidVessel.Hydra, "Hydra" },
{ ThargoidVessel.Hunter, "Hunter" },
}; };
public static ThargoidVessel GetVesselByPayout(ulong payout) { public static ThargoidVessel GetVesselByPayout(ulong payout) {

View File

@@ -0,0 +1,28 @@
using EDPlayerJournal;
using EDPlayerJournal.Entries;
using EDPlayerJournal.BGS;
namespace EDPlayerJournalTests;
[TestClass]
public class CombatZoneTest {
[TestMethod]
public void DropshipDeployTest() {
TransactionParser parser = new();
List<Entry>? entries = Helper.LoadTestData("dropship-deploy.txt");
Assert.IsNotNull(entries);
List<Transaction>? transactions = parser.Parse(entries);
Assert.IsNotNull(transactions);
Vouchers? vouchers = transactions[0] as Vouchers;
Assert.IsNotNull(vouchers);
CombatZone? combatzone = transactions[1] as CombatZone;
Assert.IsNotNull(combatzone);
Assert.AreEqual(combatzone.Type, CombatZones.GroundCombatZone);
Assert.AreEqual(combatzone.Grade, CombatZones.DifficultyHigh);
}
}

View File

@@ -0,0 +1,2 @@
{ "timestamp":"2022-11-20T14:30:30Z", "event":"RedeemVoucher", "Type":"bounty", "Amount":3197549, "Factions":[ { "Faction":"Nova Paresa", "Amount":3197549 } ] }
{ "timestamp":"2022-11-20T14:30:34Z", "event":"RedeemVoucher", "Type":"bounty", "Amount":4785831, "Factions":[ { "Faction":"", "Amount":723175 }, { "Faction":"Nova Paresa", "Amount":3197549 }, { "Faction":"Prismatic Imperium", "Amount":4062656 } ] }

View File

@@ -26,6 +26,12 @@
<None Update="double-support.txt"> <None Update="double-support.txt">
<CopyToOutputDirectory>Always</CopyToOutputDirectory> <CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None> </None>
<None Update="dropship-deploy.txt">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="legacy-transaction.txt">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="mission-failed.txt"> <None Update="mission-failed.txt">
<CopyToOutputDirectory>Always</CopyToOutputDirectory> <CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None> </None>
@@ -44,6 +50,9 @@
<None Update="SellOrganicData.txt"> <None Update="SellOrganicData.txt">
<CopyToOutputDirectory>Always</CopyToOutputDirectory> <CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None> </None>
<None Update="ThargoidBonds.txt">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="ThargoidKills.txt"> <None Update="ThargoidKills.txt">
<CopyToOutputDirectory>Always</CopyToOutputDirectory> <CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None> </None>

View File

@@ -21,6 +21,8 @@ public class FileHeaderTest {
Assert.AreEqual(header.Language, "English/UK"); Assert.AreEqual(header.Language, "English/UK");
Assert.AreEqual(header.Odyssey, true); Assert.AreEqual(header.Odyssey, true);
Assert.AreEqual(header.GameVersion, "4.0.0.1450"); Assert.AreEqual(header.GameVersion, "4.0.0.1450");
Assert.AreEqual(header.IsLegacy, false);
Assert.AreEqual(header.IsLive, true);
// Someone at FDev messed up string building there. // Someone at FDev messed up string building there.
Assert.AreEqual(header.Build, "r286858/r0 "); Assert.AreEqual(header.Build, "r286858/r0 ");
} }
@@ -44,6 +46,8 @@ public class FileHeaderTest {
Assert.AreEqual(header.Language, """English\UK"""); Assert.AreEqual(header.Language, """English\UK""");
Assert.AreEqual(header.Odyssey, false); Assert.AreEqual(header.Odyssey, false);
Assert.AreEqual(header.GameVersion, "3.5.3.400 EDH"); Assert.AreEqual(header.GameVersion, "3.5.3.400 EDH");
Assert.AreEqual(header.IsLegacy, true);
Assert.AreEqual(header.IsLive, false);
// ~73k commits in two years. Not bad. // ~73k commits in two years. Not bad.
Assert.AreEqual(header.Build, "r213094/r0 "); Assert.AreEqual(header.Build, "r213094/r0 ");
} }

View File

@@ -59,9 +59,9 @@ public class MissionTest {
Assert.IsTrue(e.IsEmptyFaction); Assert.IsTrue(e.IsEmptyFaction);
Assert.AreEqual(e.Faction, string.Empty); Assert.AreEqual(e.Faction, string.Empty);
string? influence = m.GetInfluenceForFaction("", 251012319587UL); var influence = m.GetInfluenceForFaction("", 251012319587UL);
Assert.IsNotNull(influence); Assert.IsNotNull(influence);
Assert.AreEqual(influence, "+"); Assert.AreEqual(influence[0].Influence, "+");
e = m.FactionEffects[1]; e = m.FactionEffects[1];
Assert.AreEqual(e.Faction, "Social LHS 6103 Confederation"); Assert.AreEqual(e.Faction, "Social LHS 6103 Confederation");
@@ -101,22 +101,25 @@ public class MissionTest {
Assert.AreEqual(effect.Reputation, "++"); Assert.AreEqual(effect.Reputation, "++");
string? influence; var influence = m.GetInfluenceForFaction("Salus Imperial Society", 1865919973739UL);
Assert.IsNotNull(influence);
influence = m.GetInfluenceForFaction("Salus Imperial Society", 1865919973739UL); Assert.IsTrue(influence.Length > 0);
Assert.AreEqual(influence, "++"); Assert.AreEqual(influence[0].Influence, "++");
influence = m.GetInfluenceForFaction("Salus Imperial Society", 1733186884306UL); influence = m.GetInfluenceForFaction("Salus Imperial Society", 1733186884306UL);
Assert.AreEqual(influence, "++"); Assert.IsNotNull(influence);
Assert.IsTrue(influence.Length > 0);
Assert.AreEqual(influence[0].Influence, "++");
influence = m.GetInfluenceForFaction("Saelishi Saxons", 1733186884306UL); influence = m.GetInfluenceForFaction("Saelishi Saxons", 1733186884306UL);
Assert.IsNull(influence); Assert.IsNotNull(influence);
Assert.AreEqual(influence.Length, 0);
// Only one entry are we only have Salus // Only one entry are we only have Salus
Assert.AreEqual(m.Influences.Count, 1); Assert.AreEqual(m.Influences.Count, 1);
Assert.AreEqual(m.Influences["Salus Imperial Society"].Count, 2); Assert.AreEqual(m.Influences["Salus Imperial Society"].Count, 2);
Assert.AreEqual(m.Influences["Salus Imperial Society"][1865919973739UL], "++"); Assert.AreEqual(m.Influences["Salus Imperial Society"][1865919973739UL].Influence, "++");
Assert.AreEqual(m.Influences["Salus Imperial Society"][1733186884306UL], "++"); Assert.AreEqual(m.Influences["Salus Imperial Society"][1733186884306UL].Influence, "++");
} }
[TestMethod] [TestMethod]

View File

@@ -6,6 +6,35 @@ namespace EDPlayerJournalTests;
[TestClass] [TestClass]
public class TestTransactionParser { public class TestTransactionParser {
[TestMethod]
public void Legacy() {
TransactionParser parser = new();
// File header, followed by two legacy transactions, followed by another
// file header with an Odyssey transaction
List<Entry>? entries = Helper.LoadTestData("legacy-transaction.txt");
Assert.IsNotNull(entries, "could not load test data");
if (entries == null) {
return;
}
var options = new TransactionParserOptions() {
IgnoreInfluenceSupport = false,
IgnoreExoBiology = false,
IgnoreFleetCarrierFaction = false,
IgnoreMarketBuy = false,
};
List<Transaction>? transactions = parser.Parse(entries, options);
Assert.IsNotNull(transactions, "could not parse entries");
Assert.AreEqual(transactions.Count, 3);
Assert.AreEqual(transactions[0].IsLegacy, true);
Assert.AreEqual(transactions[1].IsLegacy, true);
Assert.AreEqual(transactions[2].IsLegacy, false);
}
[TestMethod] [TestMethod]
public void DoubleFiveINF() { public void DoubleFiveINF() {
TransactionParser parser = new(); TransactionParser parser = new();
@@ -121,7 +150,14 @@ public class TestTransactionParser {
return; return;
} }
List<Transaction>? transactions = parser.Parse(entries); var options = new TransactionParserOptions() {
IgnoreInfluenceSupport = false,
IgnoreExoBiology = false,
IgnoreFleetCarrierFaction = false,
IgnoreMarketBuy = false,
};
List<Transaction>? transactions = parser.Parse(entries, options);
Assert.IsNotNull(transactions, "could not parse entries"); Assert.IsNotNull(transactions, "could not parse entries");
Assert.AreEqual(transactions.Count, 1); Assert.AreEqual(transactions.Count, 1);
Assert.IsInstanceOfType(transactions[0], typeof(OrganicData), "result is not of type Organic Data"); Assert.IsInstanceOfType(transactions[0], typeof(OrganicData), "result is not of type Organic Data");

View File

@@ -0,0 +1,3 @@
{ "timestamp":"2022-11-25T10:10:43Z", "event":"FSDJump", "Taxi":false, "Multicrew":false, "StarSystem":"Nyalayan", "SystemAddress":1458309042898, "StarPos":[29.71875,-172.71875,-6.46875], "SystemAllegiance":"Independent", "SystemEconomy":"$economy_Agri;", "SystemEconomy_Localised":"Agriculture", "SystemSecondEconomy":"$economy_Refinery;", "SystemSecondEconomy_Localised":"Refinery", "SystemGovernment":"$government_Theocracy;", "SystemGovernment_Localised":"Theocracy", "SystemSecurity":"$SYSTEM_SECURITY_medium;", "SystemSecurity_Localised":"Medium Security", "Population":1920608781, "Body":"Nyalayan A", "BodyID":1, "BodyType":"Star", "JumpDist":7.769, "FuelUsed":0.142448, "FuelLevel":15.857553, "Factions":[ { "Name":"Nyalayan Imperial Society", "FactionState":"Election", "Government":"Patronage", "Influence":0.165138, "Allegiance":"Empire", "Happiness":"$Faction_HappinessBand2;", "Happiness_Localised":"Happy", "MyReputation":17.496799, "ActiveStates":[ { "State":"Election" } ] }, { "Name":"Reret Emperor's Grace", "FactionState":"War", "Government":"Patronage", "Influence":0.257900, "Allegiance":"Empire", "Happiness":"$Faction_HappinessBand2;", "Happiness_Localised":"Happy", "MyReputation":100.000000, "ActiveStates":[ { "State":"Expansion" }, { "State":"War" } ] }, { "Name":"Nyalayan Silver Transport Org", "FactionState":"None", "Government":"Corporate", "Influence":0.076453, "Allegiance":"Empire", "Happiness":"$Faction_HappinessBand2;", "Happiness_Localised":"Happy", "MyReputation":81.875000 }, { "Name":"Nyalayan Power & Co", "FactionState":"None", "Government":"Corporate", "Influence":0.060143, "Allegiance":"Independent", "Happiness":"$Faction_HappinessBand2;", "Happiness_Localised":"Happy", "MyReputation":23.173201 }, { "Name":"Amanogawa Enlight", "FactionState":"War", "Government":"Theocracy", "Influence":0.257900, "Allegiance":"Independent", "Happiness":"$Faction_HappinessBand2;", "Happiness_Localised":"Happy", "MyReputation":-1.210600, "ActiveStates":[ { "State":"Expansion" }, { "State":"War" } ] }, { "Name":"Nyalayan Crimson Dragons", "FactionState":"None", "Government":"Anarchy", "Influence":0.017329, "Allegiance":"Independent", "Happiness":"$Faction_HappinessBand2;", "Happiness_Localised":"Happy", "MyReputation":-13.530000 }, { "Name":"Traditional Nyalayan Front", "FactionState":"Election", "Government":"Dictatorship", "Influence":0.165138, "Allegiance":"Independent", "Happiness":"$Faction_HappinessBand2;", "Happiness_Localised":"Happy", "MyReputation":64.699997, "ActiveStates":[ { "State":"Election" } ] } ], "SystemFaction":{ "Name":"Amanogawa Enlight", "FactionState":"War" }, "Conflicts":[ { "WarType":"election", "Status":"active", "Faction1":{ "Name":"Nyalayan Imperial Society", "Stake":"Pilcher Port", "WonDays":2 }, "Faction2":{ "Name":"Traditional Nyalayan Front", "Stake":"Lamar Falls", "WonDays":0 } }, { "WarType":"war", "Status":"active", "Faction1":{ "Name":"Reret Emperor's Grace", "Stake":"", "WonDays":1 }, "Faction2":{ "Name":"Amanogawa Enlight", "Stake":"Sutter Ring", "WonDays":2 } } ] }
{ "timestamp":"2022-11-25T10:13:32Z", "event":"Docked", "StationName":"Pilcher Port", "StationType":"Orbis", "Taxi":false, "Multicrew":false, "StarSystem":"Nyalayan", "SystemAddress":1458309042898, "MarketID":3222853120, "StationFaction":{ "Name":"Nyalayan Imperial Society", "FactionState":"Election" }, "StationGovernment":"$government_Patronage;", "StationGovernment_Localised":"Patronage", "StationAllegiance":"Empire", "StationServices":[ "dock", "autodock", "commodities", "contacts", "exploration", "missions", "outfitting", "crewlounge", "rearm", "refuel", "repair", "shipyard", "tuning", "engineer", "missionsgenerated", "flightcontroller", "stationoperations", "powerplay", "searchrescue", "stationMenu", "shop", "livery", "socialspace", "bartender", "vistagenomics", "pioneersupplies", "apexinterstellar", "frontlinesolutions" ], "StationEconomy":"$economy_Refinery;", "StationEconomy_Localised":"Refinery", "StationEconomies":[ { "Name":"$economy_Refinery;", "Name_Localised":"Refinery", "Proportion":1.000000 } ], "DistFromStarLS":76.465557, "LandingPads":{ "Small":8, "Medium":11, "Large":6 } }
{ "timestamp":"2022-11-25T10:13:53Z", "event":"RedeemVoucher", "Type":"CombatBond", "Amount":24240000, "Faction":"PilotsFederation" }

View File

@@ -1,10 +1,6 @@
using EDPlayerJournal.BGS; using EDPlayerJournal;
using EDPlayerJournal.BGS;
using EDPlayerJournal.Entries; using EDPlayerJournal.Entries;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace EDPlayerJournalTests; namespace EDPlayerJournalTests;
@@ -21,13 +17,41 @@ public class ThargoidKills {
return; return;
} }
List<Transaction>? transactions = parser.Parse(entries); List<ThargoidKill>? transactions = parser.Parse(entries)?.OfType<ThargoidKill>().ToList();
Assert.IsNotNull(transactions, "could not parse entries"); Assert.IsNotNull(transactions, "could not parse entries");
Assert.AreEqual(transactions.Count, 4); Assert.AreEqual(transactions.Count, 3);
// In recent updates the payout was changed, that's why this test reports unknown thargoid vessels
// This test makes sure the new parser does not conflict with legacy values
//
Assert.IsInstanceOfType(transactions[0], typeof(ThargoidKill), "result is not of type ThargoidKill"); Assert.IsInstanceOfType(transactions[0], typeof(ThargoidKill), "result is not of type ThargoidKill");
Assert.AreEqual(transactions[0].ThargoidType, EDPlayerJournal.ThargoidVessel.Unknown);
Assert.IsInstanceOfType(transactions[1], typeof(ThargoidKill), "result is not of type ThargoidKill"); Assert.IsInstanceOfType(transactions[1], typeof(ThargoidKill), "result is not of type ThargoidKill");
Assert.AreEqual(transactions[1].ThargoidType, EDPlayerJournal.ThargoidVessel.Unknown);
Assert.IsInstanceOfType(transactions[2], typeof(ThargoidKill), "result is not of type ThargoidKill"); Assert.IsInstanceOfType(transactions[2], typeof(ThargoidKill), "result is not of type ThargoidKill");
Assert.AreEqual(transactions[2].ThargoidType, EDPlayerJournal.ThargoidVessel.Unknown);
}
[TestMethod]
public void ThargoidBonds() {
TransactionParser parser = new();
List<Entry>? entries = Helper.LoadTestData("ThargoidBonds.txt");
Assert.IsNotNull(entries, "could not load test data");
if (entries == null) {
return;
}
List<Vouchers>? transactions = parser.Parse(entries)?.OfType<Vouchers>().ToList();
Assert.IsNotNull(transactions, "could not parse entries");
Assert.AreEqual(transactions.Count, 1);
Assert.AreEqual(Factions.IsPilotsFederation(transactions[0].Faction), true);
Assert.AreEqual(transactions[0].TotalSum, 24240000L);
Assert.AreEqual(transactions[0].Type, "Combat Bond");
} }
} }

View File

@@ -1,4 +1,3 @@
{ "timestamp":"2022-11-25T09:50:45Z", "event":"FactionKillBond", "Reward":80000, "AwardingFaction":"$faction_PilotsFederation;", "AwardingFaction_Localised":"Pilots' Federation", "VictimFaction":"$faction_Thargoid;", "VictimFaction_Localised":"Thargoids" } { "timestamp":"2022-11-25T09:50:45Z", "event":"FactionKillBond", "Reward":80000, "AwardingFaction":"$faction_PilotsFederation;", "AwardingFaction_Localised":"Pilots' Federation", "VictimFaction":"$faction_Thargoid;", "VictimFaction_Localised":"Thargoids" }
{ "timestamp":"2022-11-25T09:52:28Z", "event":"FactionKillBond", "Reward":24000000, "AwardingFaction":"$faction_PilotsFederation;", "AwardingFaction_Localised":"Pilots' Federation", "VictimFaction":"$faction_Thargoid;", "VictimFaction_Localised":"Thargoids" } { "timestamp":"2022-11-25T09:52:28Z", "event":"FactionKillBond", "Reward":24000000, "AwardingFaction":"$faction_PilotsFederation;", "AwardingFaction_Localised":"Pilots' Federation", "VictimFaction":"$faction_Thargoid;", "VictimFaction_Localised":"Thargoids" }
{ "timestamp":"2022-11-25T09:47:19Z", "event":"FactionKillBond", "Reward":80000, "AwardingFaction":"$faction_PilotsFederation;", "AwardingFaction_Localised":"Pilots' Federation", "VictimFaction":"$faction_Thargoid;", "VictimFaction_Localised":"Thargoids" } { "timestamp":"2022-11-25T09:47:19Z", "event":"FactionKillBond", "Reward":80000, "AwardingFaction":"$faction_PilotsFederation;", "AwardingFaction_Localised":"Pilots' Federation", "VictimFaction":"$faction_Thargoid;", "VictimFaction_Localised":"Thargoids" }
{ "timestamp":"2022-11-25T10:13:53Z", "event":"RedeemVoucher", "Type":"CombatBond", "Amount":24240000, "Faction":"PilotsFederation" }

View File

@@ -0,0 +1,6 @@
{ "timestamp":"2022-11-25T10:10:43Z", "event":"FSDJump", "Taxi":false, "Multicrew":false, "StarSystem":"Nyalayan", "SystemAddress":1458309042898, "StarPos":[29.71875,-172.71875,-6.46875], "SystemAllegiance":"Independent", "SystemEconomy":"$economy_Agri;", "SystemEconomy_Localised":"Agriculture", "SystemSecondEconomy":"$economy_Refinery;", "SystemSecondEconomy_Localised":"Refinery", "SystemGovernment":"$government_Theocracy;", "SystemGovernment_Localised":"Theocracy", "SystemSecurity":"$SYSTEM_SECURITY_medium;", "SystemSecurity_Localised":"Medium Security", "Population":1920608781, "Body":"Nyalayan A", "BodyID":1, "BodyType":"Star", "JumpDist":7.769, "FuelUsed":0.142448, "FuelLevel":15.857553, "Factions":[ { "Name":"Nyalayan Imperial Society", "FactionState":"Election", "Government":"Patronage", "Influence":0.165138, "Allegiance":"Empire", "Happiness":"$Faction_HappinessBand2;", "Happiness_Localised":"Happy", "MyReputation":17.496799, "ActiveStates":[ { "State":"Election" } ] }, { "Name":"Reret Emperor's Grace", "FactionState":"War", "Government":"Patronage", "Influence":0.257900, "Allegiance":"Empire", "Happiness":"$Faction_HappinessBand2;", "Happiness_Localised":"Happy", "MyReputation":100.000000, "ActiveStates":[ { "State":"Expansion" }, { "State":"War" } ] }, { "Name":"Nyalayan Silver Transport Org", "FactionState":"None", "Government":"Corporate", "Influence":0.076453, "Allegiance":"Empire", "Happiness":"$Faction_HappinessBand2;", "Happiness_Localised":"Happy", "MyReputation":81.875000 }, { "Name":"Nyalayan Power & Co", "FactionState":"None", "Government":"Corporate", "Influence":0.060143, "Allegiance":"Independent", "Happiness":"$Faction_HappinessBand2;", "Happiness_Localised":"Happy", "MyReputation":23.173201 }, { "Name":"Amanogawa Enlight", "FactionState":"War", "Government":"Theocracy", "Influence":0.257900, "Allegiance":"Independent", "Happiness":"$Faction_HappinessBand2;", "Happiness_Localised":"Happy", "MyReputation":-1.210600, "ActiveStates":[ { "State":"Expansion" }, { "State":"War" } ] }, { "Name":"Nyalayan Crimson Dragons", "FactionState":"None", "Government":"Anarchy", "Influence":0.017329, "Allegiance":"Independent", "Happiness":"$Faction_HappinessBand2;", "Happiness_Localised":"Happy", "MyReputation":-13.530000 }, { "Name":"Traditional Nyalayan Front", "FactionState":"Election", "Government":"Dictatorship", "Influence":0.165138, "Allegiance":"Independent", "Happiness":"$Faction_HappinessBand2;", "Happiness_Localised":"Happy", "MyReputation":64.699997, "ActiveStates":[ { "State":"Election" } ] } ], "SystemFaction":{ "Name":"Amanogawa Enlight", "FactionState":"War" }, "Conflicts":[ { "WarType":"election", "Status":"active", "Faction1":{ "Name":"Nyalayan Imperial Society", "Stake":"Pilcher Port", "WonDays":2 }, "Faction2":{ "Name":"Traditional Nyalayan Front", "Stake":"Lamar Falls", "WonDays":0 } }, { "WarType":"war", "Status":"active", "Faction1":{ "Name":"Reret Emperor's Grace", "Stake":"", "WonDays":1 }, "Faction2":{ "Name":"Amanogawa Enlight", "Stake":"Sutter Ring", "WonDays":2 } } ] }
{ "timestamp":"2022-11-25T09:50:45Z", "event":"FactionKillBond", "Reward":80000, "AwardingFaction":"$faction_PilotsFederation;", "AwardingFaction_Localised":"Pilots' Federation", "VictimFaction":"$faction_Thargoid;", "VictimFaction_Localised":"Thargoids" }
{ "timestamp":"2022-11-25T09:52:28Z", "event":"FactionKillBond", "Reward":24000000, "AwardingFaction":"$faction_PilotsFederation;", "AwardingFaction_Localised":"Pilots' Federation", "VictimFaction":"$faction_Thargoid;", "VictimFaction_Localised":"Thargoids" }
{ "timestamp":"2022-11-25T09:47:19Z", "event":"FactionKillBond", "Reward":80000, "AwardingFaction":"$faction_PilotsFederation;", "AwardingFaction_Localised":"Pilots' Federation", "VictimFaction":"$faction_Thargoid;", "VictimFaction_Localised":"Thargoids" }
{ "timestamp":"2022-11-25T10:13:32Z", "event":"Docked", "StationName":"Pilcher Port", "StationType":"Orbis", "Taxi":false, "Multicrew":false, "StarSystem":"Nyalayan", "SystemAddress":1458309042898, "MarketID":3222853120, "StationFaction":{ "Name":"Nyalayan Imperial Society", "FactionState":"Election" }, "StationGovernment":"$government_Patronage;", "StationGovernment_Localised":"Patronage", "StationAllegiance":"Empire", "StationServices":[ "dock", "autodock", "commodities", "contacts", "exploration", "missions", "outfitting", "crewlounge", "rearm", "refuel", "repair", "shipyard", "tuning", "engineer", "missionsgenerated", "flightcontroller", "stationoperations", "powerplay", "searchrescue", "stationMenu", "shop", "livery", "socialspace", "bartender", "vistagenomics", "pioneersupplies", "apexinterstellar", "frontlinesolutions" ], "StationEconomy":"$economy_Refinery;", "StationEconomy_Localised":"Refinery", "StationEconomies":[ { "Name":"$economy_Refinery;", "Name_Localised":"Refinery", "Proportion":1.000000 } ], "DistFromStarLS":76.465557, "LandingPads":{ "Small":8, "Medium":11, "Large":6 } }
{ "timestamp":"2022-11-25T10:13:53Z", "event":"RedeemVoucher", "Type":"CombatBond", "Amount":24240000, "Faction":"PilotsFederation" }

View File

@@ -0,0 +1,63 @@
{"timestamp":"2022-12-09T14:23:52Z","event":"Fileheader","part":1,"language":"English/UK","Odyssey":true,"gameversion":"4.0.0.1476","build":"r289925/r0 "}
{"timestamp":"2022-12-09T14:33:01Z","event":"Location","DistFromStarLS":774.492896,"Docked":false,"OnFoot":true,"StarSystem":"Kazahua","SystemAddress":2871050905001,"StarPos":[93.28125,-180.25,14.6875],"SystemAllegiance":"Empire","SystemEconomy":"$economy_Industrial;","SystemEconomy_Localised":"Industrial","SystemSecondEconomy":"$economy_Colony;","SystemSecondEconomy_Localised":"Colony","SystemGovernment":"$government_Patronage;","SystemGovernment_Localised":"Patronage","SystemSecurity":"$SYSTEM_SECURITY_low;","SystemSecurity_Localised":"Low Security","Population":17949,"Body":"Kazahua 4 a","BodyID":5,"BodyType":"Planet","Factions":[{"Name":"Kazahua Co","FactionState":"None","Government":"Corporate","Influence":0.049098,"Allegiance":"Empire","Happiness":"$Faction_HappinessBand2;","Happiness_Localised":"Happy","MyReputation":0.0},{"Name":"Kazahua Crimson Ring","FactionState":"None","Government":"Anarchy","Influence":0.01002,"Allegiance":"Independent","Happiness":"$Faction_HappinessBand2;","Happiness_Localised":"Happy","MyReputation":0.0},{"Name":"HIP 10611 Shared","FactionState":"None","Government":"Cooperative","Influence":0.091182,"Allegiance":"Independent","Happiness":"$Faction_HappinessBand2;","Happiness_Localised":"Happy","MyReputation":2.97},{"Name":"Traditional Yao Tzu Liberty Party","FactionState":"None","Government":"Dictatorship","Influence":0.058116,"Allegiance":"Empire","Happiness":"$Faction_HappinessBand2;","Happiness_Localised":"Happy","MyReputation":-10.56},{"Name":"Sapii allied","FactionState":"War","Government":"Cooperative","Influence":0.395792,"Allegiance":"Independent","Happiness":"$Faction_HappinessBand2;","Happiness_Localised":"Happy","MyReputation":-6.0,"ActiveStates":[{"State":"War"}]},{"Name":"Nova Paresa","FactionState":"War","Government":"Patronage","Influence":0.395792,"Allegiance":"Empire","Happiness":"$Faction_HappinessBand2;","Happiness_Localised":"Happy","SquadronFaction":true,"MyReputation":63.045399,"ActiveStates":[{"State":"War"}]}],"SystemFaction":{"Name":"Nova Paresa","FactionState":"War"},"Conflicts":[{"WarType":"war","Status":"active","Faction1":{"Name":"Sapii allied","Stake":"Barmin Installation","WonDays":0},"Faction2":{"Name":"Nova Paresa","Stake":"Rabinowitz Colony","WonDays":0}}]}
{"timestamp":"2022-12-09T14:33:05Z","event":"ReceiveText","From":"Barmin Installation","Message":"$STATION_NoFireZone_entered;","Message_Localised":"No fire zone entered.","Channel":"npc"}
{"timestamp":"2022-12-09T14:36:52Z","event":"ReceiveText","From":"$ShipName_Police_Independent;","From_Localised":"System Authority Vessel","Message":"$Police_EndPatrol02;","Message_Localised":"Requesting docking clearance, Control.","Channel":"npc"}
{"timestamp":"2022-12-09T14:39:54Z","event":"ReceiveText","From":"$ShipName_Police_Independent;","From_Localised":"System Authority Vessel","Message":"$Police_StartPatrol01;","Message_Localised":"Patrol craft checking in. Pre-flight checks complete, I'm heading out.","Channel":"npc"}
{"timestamp":"2022-12-09T14:42:02Z","event":"ReceiveText","From":"$ShipName_Police_Independent;","From_Localised":"System Authority Vessel","Message":"$Police_EndPatrol04;","Message_Localised":"Negative, Tower, I'm bingo on fuel, RTB.","Channel":"npc"}
{"timestamp":"2022-12-09T14:42:05Z","event":"ReceiveText","From":"$ShipName_Police_Independent;","From_Localised":"System Authority Vessel","Message":"$Police_EndPatrol02;","Message_Localised":"Requesting docking clearance, Control.","Channel":"npc"}
{"timestamp":"2022-12-09T14:45:23Z","event":"ReceiveText","From":"$ShipName_Police_Independent;","From_Localised":"System Authority Vessel","Message":"$Police_StartPatrol01;","Message_Localised":"Patrol craft checking in. Pre-flight checks complete, I'm heading out.","Channel":"npc"}
{"timestamp":"2022-12-09T14:45:51Z","event":"ReceiveText","From":"$ShipName_Police_Independent;","From_Localised":"System Authority Vessel","Message":"$Police_StartPatrol04;","Message_Localised":"I'm away, heading to patrol route alpha now.","Channel":"npc"}
{"timestamp":"2022-12-09T14:46:22Z","event":"RedeemVoucher","Type":"CombatBond","Amount":17406041,"Faction":"Nova Paresa"}
{"timestamp":"2022-12-09T14:49:17Z","event":"ReceiveText","From":"$ShipName_Police_Independent;","From_Localised":"System Authority Vessel","Message":"$Police_EndPatrol02;","Message_Localised":"Requesting docking clearance, Control.","Channel":"npc"}
{"timestamp":"2022-12-09T14:49:43Z","event":"ReceiveText","From":"$ShipName_Police_Independent;","From_Localised":"System Authority Vessel","Message":"$Police_EndPatrol05;","Message_Localised":"Patrol completed, Command, I'm heading home.","Channel":"npc"}
{"timestamp":"2022-12-09T14:52:32Z","event":"ReceiveText","From":"$ShipName_Police_Independent;","From_Localised":"System Authority Vessel","Message":"$Police_StartPatrol01;","Message_Localised":"Patrol craft checking in. Pre-flight checks complete, I'm heading out.","Channel":"npc"}
{"timestamp":"2022-12-09T14:52:34Z","event":"Embark","SRV":false,"Taxi":true,"Multicrew":false,"StarSystem":"Kazahua","SystemAddress":2871050905001,"Body":"Kazahua 4 a","BodyID":5,"OnStation":false,"OnPlanet":true}
{"timestamp":"2022-12-09T14:53:29Z","event":"SupercruiseEntry","Taxi":true,"Multicrew":false,"StarSystem":"Kazahua","SystemAddress":2871050905001}
{"timestamp":"2022-12-09T14:58:16Z","event":"DropshipDeploy","StarSystem":"Kazahua","SystemAddress":2871050905001,"Body":"Kazahua 2","BodyID":2,"OnStation":false,"OnPlanet":true}
{"timestamp":"2022-12-09T14:59:38Z","event":"FactionKillBond","Reward":65358,"AwardingFaction":"Nova Paresa","VictimFaction":"Sapii allied"}
{"timestamp":"2022-12-09T15:00:02Z","event":"FactionKillBond","Reward":52500,"AwardingFaction":"Nova Paresa","VictimFaction":"Sapii allied"}
{"timestamp":"2022-12-09T15:00:12Z","event":"FactionKillBond","Reward":39642,"AwardingFaction":"Nova Paresa","VictimFaction":"Sapii allied"}
{"timestamp":"2022-12-09T15:00:33Z","event":"FactionKillBond","Reward":52500,"AwardingFaction":"Nova Paresa","VictimFaction":"Sapii allied"}
{"timestamp":"2022-12-09T15:00:45Z","event":"FactionKillBond","Reward":65358,"AwardingFaction":"Nova Paresa","VictimFaction":"Sapii allied"}
{"timestamp":"2022-12-09T15:01:07Z","event":"FactionKillBond","Reward":87362,"AwardingFaction":"Nova Paresa","VictimFaction":"Sapii allied"}
{"timestamp":"2022-12-09T15:01:11Z","event":"ReceiveText","From":"$ShipName_Military_Independent;","From_Localised":"System Defence Force","Message":"$Military_Passthrough06;","Message_Localised":"On patrol. Scanning for hostiles.","Channel":"npc"}
{"timestamp":"2022-12-09T15:02:12Z","event":"FactionKillBond","Reward":65358,"AwardingFaction":"Nova Paresa","VictimFaction":"Sapii allied"}
{"timestamp":"2022-12-09T15:02:21Z","event":"FactionKillBond","Reward":52500,"AwardingFaction":"Nova Paresa","VictimFaction":"Sapii allied"}
{"timestamp":"2022-12-09T15:02:24Z","event":"FactionKillBond","Reward":65358,"AwardingFaction":"Nova Paresa","VictimFaction":"Sapii allied"}
{"timestamp":"2022-12-09T15:02:32Z","event":"FactionKillBond","Reward":65358,"AwardingFaction":"Nova Paresa","VictimFaction":"Sapii allied"}
{"timestamp":"2022-12-09T15:02:42Z","event":"FactionKillBond","Reward":87362,"AwardingFaction":"Nova Paresa","VictimFaction":"Sapii allied"}
{"timestamp":"2022-12-09T15:02:45Z","event":"FactionKillBond","Reward":65358,"AwardingFaction":"Nova Paresa","VictimFaction":"Sapii allied"}
{"timestamp":"2022-12-09T15:02:50Z","event":"ReceiveText","From":"$ShipName_Military_Independent;","From_Localised":"System Defence Force","Message":"$Military_Passthrough09;","Message_Localised":"This is a routine patrol. There's nothing to worry about.","Channel":"npc"}
{"timestamp":"2022-12-09T15:04:03Z","event":"FactionKillBond","Reward":39642,"AwardingFaction":"Nova Paresa","VictimFaction":"Sapii allied"}
{"timestamp":"2022-12-09T15:04:03Z","event":"ReceiveText","From":"$ShipName_Military_Independent;","From_Localised":"System Defence Force","Message":"$Military_Passthrough06;","Message_Localised":"On patrol. Scanning for hostiles.","Channel":"npc"}
{"timestamp":"2022-12-09T15:04:09Z","event":"FactionKillBond","Reward":52500,"AwardingFaction":"Nova Paresa","VictimFaction":"Sapii allied"}
{"timestamp":"2022-12-09T15:04:13Z","event":"FactionKillBond","Reward":87362,"AwardingFaction":"Nova Paresa","VictimFaction":"Sapii allied"}
{"timestamp":"2022-12-09T15:04:25Z","event":"FactionKillBond","Reward":87362,"AwardingFaction":"Nova Paresa","VictimFaction":"Sapii allied"}
{"timestamp":"2022-12-09T15:05:06Z","event":"ReceiveText","From":"$ShipName_Military_Independent;","From_Localised":"System Defence Force","Message":"$Military_Passthrough06;","Message_Localised":"On patrol. Scanning for hostiles.","Channel":"npc"}
{"timestamp":"2022-12-09T15:05:10Z","event":"ReceiveText","From":"$ShipName_Military_Independent;","From_Localised":"System Defence Force","Message":"$Military_Passthrough10;","Message_Localised":"System sweep in operation.","Channel":"npc"}
{"timestamp":"2022-12-09T15:05:19Z","event":"ReceiveText","From":"$ShipName_Military_Independent;","From_Localised":"System Defence Force","Message":"$Military_Passthrough06;","Message_Localised":"On patrol. Scanning for hostiles.","Channel":"npc"}
{"timestamp":"2022-12-09T15:05:22Z","event":"FactionKillBond","Reward":65358,"AwardingFaction":"Nova Paresa","VictimFaction":"Sapii allied"}
{"timestamp":"2022-12-09T15:05:35Z","event":"FactionKillBond","Reward":87362,"AwardingFaction":"Nova Paresa","VictimFaction":"Sapii allied"}
{"timestamp":"2022-12-09T15:05:44Z","event":"FactionKillBond","Reward":65358,"AwardingFaction":"Nova Paresa","VictimFaction":"Sapii allied"}
{"timestamp":"2022-12-09T15:06:11Z","event":"FactionKillBond","Reward":65358,"AwardingFaction":"Nova Paresa","VictimFaction":"Sapii allied"}
{"timestamp":"2022-12-09T15:06:22Z","event":"ReceiveText","From":"$ShipName_Military_Independent;","From_Localised":"System Defence Force","Message":"$Military_Passthrough08;","Message_Localised":"Patrol waypoint reached. Scanning.","Channel":"npc"}
{"timestamp":"2022-12-09T15:06:27Z","event":"FactionKillBond","Reward":52500,"AwardingFaction":"Nova Paresa","VictimFaction":"Sapii allied"}
{"timestamp":"2022-12-09T15:06:27Z","event":"FactionKillBond","Reward":39642,"AwardingFaction":"Nova Paresa","VictimFaction":"Sapii allied"}
{"timestamp":"2022-12-09T15:06:39Z","event":"FactionKillBond","Reward":52500,"AwardingFaction":"Nova Paresa","VictimFaction":"Sapii allied"}
{"timestamp":"2022-12-09T15:06:44Z","event":"ReceiveText","From":"$ShipName_Military_Independent;","From_Localised":"System Defence Force","Message":"$Military_Passthrough09;","Message_Localised":"This is a routine patrol. There's nothing to worry about.","Channel":"npc"}
{"timestamp":"2022-12-09T15:06:50Z","event":"FactionKillBond","Reward":65358,"AwardingFaction":"Nova Paresa","VictimFaction":"Sapii allied"}
{"timestamp":"2022-12-09T15:07:03Z","event":"FactionKillBond","Reward":65358,"AwardingFaction":"Nova Paresa","VictimFaction":"Sapii allied"}
{"timestamp":"2022-12-09T15:07:11Z","event":"FactionKillBond","Reward":39642,"AwardingFaction":"Nova Paresa","VictimFaction":"Sapii allied"}
{"timestamp":"2022-12-09T15:07:15Z","event":"FactionKillBond","Reward":52500,"AwardingFaction":"Nova Paresa","VictimFaction":"Sapii allied"}
{"timestamp":"2022-12-09T15:07:26Z","event":"FactionKillBond","Reward":87362,"AwardingFaction":"Nova Paresa","VictimFaction":"Sapii allied"}
{"timestamp":"2022-12-09T15:08:20Z","event":"FactionKillBond","Reward":52500,"AwardingFaction":"Nova Paresa","VictimFaction":"Sapii allied"}
{"timestamp":"2022-12-09T15:08:26Z","event":"FactionKillBond","Reward":65358,"AwardingFaction":"Nova Paresa","VictimFaction":"Sapii allied"}
{"timestamp":"2022-12-09T15:08:29Z","event":"ReceiveText","From":"$ShipName_Military_Independent;","From_Localised":"System Defence Force","Message":"$Military_Passthrough07;","Message_Localised":"No need to worry, the navy is here.","Channel":"npc"}
{"timestamp":"2022-12-09T15:08:33Z","event":"FactionKillBond","Reward":87362,"AwardingFaction":"Nova Paresa","VictimFaction":"Sapii allied"}
{"timestamp":"2022-12-09T15:08:47Z","event":"FactionKillBond","Reward":87362,"AwardingFaction":"Nova Paresa","VictimFaction":"Sapii allied"}
{"timestamp":"2022-12-09T15:08:48Z","event":"FactionKillBond","Reward":87362,"AwardingFaction":"Nova Paresa","VictimFaction":"Sapii allied"}
{"timestamp":"2022-12-09T15:09:00Z","event":"FactionKillBond","Reward":39642,"AwardingFaction":"Nova Paresa","VictimFaction":"Sapii allied"}
{"timestamp":"2022-12-09T15:09:00Z","event":"FactionKillBond","Reward":39642,"AwardingFaction":"Nova Paresa","VictimFaction":"Sapii allied"}
{"timestamp":"2022-12-09T15:09:08Z","event":"FactionKillBond","Reward":39642,"AwardingFaction":"Nova Paresa","VictimFaction":"Sapii allied"}
{"timestamp":"2022-12-09T15:09:20Z","event":"FactionKillBond","Reward":78777,"AwardingFaction":"Nova Paresa","VictimFaction":"Sapii allied"}
{"timestamp":"2022-12-09T15:09:46Z","event":"Shutdown"}

View File

@@ -0,0 +1,7 @@
{ "timestamp":"2022-02-06T16:36:53Z", "event":"Fileheader", "part":1, "language":"English/UK", "gameversion":"3.8.0.1102", "build":"r280672/r0 " }
{ "timestamp":"2022-02-06T18:10:26Z", "event":"FSDJump", "Taxi":false, "Multicrew":false, "StarSystem":"Akualanu", "SystemAddress":5069805856169, "StarPos":[63.78125,-128.50000,3.00000], "SystemAllegiance":"Empire", "SystemEconomy":"$economy_Tourism;", "SystemEconomy_Localised":"Tourism", "SystemSecondEconomy":"$economy_HighTech;", "SystemSecondEconomy_Localised":"High Tech", "SystemGovernment":"$government_Patronage;", "SystemGovernment_Localised":"Patronage", "SystemSecurity":"$SYSTEM_SECURITY_low;", "SystemSecurity_Localised":"Low Security", "Population":787019, "Body":"Akualanu A", "BodyID":1, "BodyType":"Star", "Powers":[ "A. Lavigny-Duval" ], "PowerplayState":"Exploited", "JumpDist":40.001, "FuelUsed":4.849240, "FuelLevel":22.573641, "Factions":[ { "Name":"Akualanu United & Co", "FactionState":"War", "Government":"Corporate", "Influence":0.158000, "Allegiance":"Empire", "Happiness":"$Faction_HappinessBand3;", "Happiness_Localised":"Discontented", "MyReputation":100.000000, "RecoveringStates":[ { "State":"InfrastructureFailure", "Trend":0 } ], "ActiveStates":[ { "State":"Lockdown" }, { "State":"Famine" }, { "State":"War" } ] }, { "Name":"Alacagui Holdings", "FactionState":"War", "Government":"Corporate", "Influence":0.086000, "Allegiance":"Empire", "Happiness":"$Faction_HappinessBand2;", "Happiness_Localised":"Happy", "MyReputation":55.000000, "RecoveringStates":[ { "State":"PirateAttack", "Trend":0 } ], "ActiveStates":[ { "State":"War" } ] }, { "Name":"Left Party of Akualanu", "FactionState":"War", "Government":"Communism", "Influence":0.086000, "Allegiance":"Independent", "Happiness":"$Faction_HappinessBand3;", "Happiness_Localised":"Discontented", "MyReputation":95.899399, "RecoveringStates":[ { "State":"InfrastructureFailure", "Trend":0 } ], "ActiveStates":[ { "State":"Lockdown" }, { "State":"Famine" }, { "State":"War" } ] }, { "Name":"Cartel of Akualanu", "FactionState":"Famine", "Government":"Anarchy", "Influence":0.028000, "Allegiance":"Independent", "Happiness":"$Faction_HappinessBand2;", "Happiness_Localised":"Happy", "MyReputation":29.040001, "RecoveringStates":[ { "State":"InfrastructureFailure", "Trend":0 } ], "ActiveStates":[ { "State":"Famine" } ] }, { "Name":"Revolutionary Akualanu Liberals", "FactionState":"Bust", "Government":"Democracy", "Influence":0.085000, "Allegiance":"Independent", "Happiness":"$Faction_HappinessBand3;", "Happiness_Localised":"Discontented", "MyReputation":43.093700, "PendingStates":[ { "State":"Lockdown", "Trend":0 } ], "ActiveStates":[ { "State":"InfrastructureFailure" }, { "State":"Bust" } ] }, { "Name":"Conservatives of Cockaigne", "FactionState":"War", "Government":"Dictatorship", "Influence":0.138000, "Allegiance":"Empire", "Happiness":"$Faction_HappinessBand3;", "Happiness_Localised":"Discontented", "MyReputation":70.000000, "ActiveStates":[ { "State":"CivilUnrest" }, { "State":"InfrastructureFailure" }, { "State":"War" } ] }, { "Name":"Nova Paresa", "FactionState":"Investment", "Government":"Patronage", "Influence":0.419000, "Allegiance":"Empire", "Happiness":"$Faction_HappinessBand1;", "Happiness_Localised":"Elated", "SquadronFaction":true, "MyReputation":100.000000, "ActiveStates":[ { "State":"Investment" }, { "State":"CivilLiberty" } ] } ], "SystemFaction":{ "Name":"Nova Paresa", "FactionState":"Investment" }, "Conflicts":[ { "WarType":"war", "Status":"active", "Faction1":{ "Name":"Akualanu United & Co", "Stake":"Konig Institution", "WonDays":0 }, "Faction2":{ "Name":"Conservatives of Cockaigne", "Stake":"", "WonDays":1 } }, { "WarType":"war", "Status":"active", "Faction1":{ "Name":"Alacagui Holdings", "Stake":"Ware Cultivation Facility", "WonDays":2 }, "Faction2":{ "Name":"Left Party of Akualanu", "Stake":"", "WonDays":2 } } ] }
{ "timestamp":"2022-02-06T18:13:30Z", "event":"Docked", "StationName":"Hughes Vista", "StationType":"Coriolis", "Taxi":false, "Multicrew":false, "StarSystem":"Akualanu", "SystemAddress":5069805856169, "MarketID":3222969088, "StationFaction":{ "Name":"Nova Paresa", "FactionState":"Investment" }, "StationGovernment":"$government_Patronage;", "StationGovernment_Localised":"Patronage", "StationAllegiance":"Empire", "StationServices":[ "dock", "autodock", "commodities", "contacts", "exploration", "missions", "outfitting", "crewlounge", "rearm", "refuel", "repair", "shipyard", "tuning", "engineer", "missionsgenerated", "facilitator", "flightcontroller", "stationoperations", "powerplay", "searchrescue", "stationMenu", "shop", "livery", "socialspace", "bartender", "vistagenomics", "pioneersupplies", "apexinterstellar", "frontlinesolutions" ], "StationEconomy":"$economy_Tourism;", "StationEconomy_Localised":"Tourism", "StationEconomies":[ { "Name":"$economy_Tourism;", "Name_Localised":"Tourism", "Proportion":1.000000 } ], "DistFromStarLS":78.917615, "LandingPads":{ "Small":13, "Medium":16, "Large":8 } }
{ "timestamp":"2022-11-25T09:52:28Z", "event":"FactionKillBond", "Reward":24000000, "AwardingFaction":"$faction_PilotsFederation;", "AwardingFaction_Localised":"Pilots' Federation", "VictimFaction":"$faction_Thargoid;", "VictimFaction_Localised":"Thargoids" }
{ "timestamp":"2022-11-21T15:56:08Z", "event":"MarketSell", "MarketID":3222176256, "Type":"silver", "Count":224, "SellPrice":40996, "TotalSale":9183104, "AvgPricePaid":34311 }
{ "timestamp":"2022-02-06T16:36:53Z", "event":"Fileheader", "part":1, "language":"English/UK", "Odyssey":true, "gameversion":"4.0.0.1102", "build":"r280672/r0 " }
{ "timestamp":"2022-02-06T18:17:44Z", "event":"SellOrganicData", "MarketID":3222969088, "BioData":[ { "Genus":"$Codex_Ent_Stratum_Genus_Name;", "Genus_Localised":"Stratum", "Species":"$Codex_Ent_Stratum_07_Name;", "Species_Localised":"Stratum Tectonicas", "Value":806300, "Bonus":0 }, { "Genus":"$Codex_Ent_Aleoids_Genus_Name;", "Genus_Localised":"Aleoida", "Species":"$Codex_Ent_Aleoids_05_Name;", "Species_Localised":"Aleoida Gravis", "Value":596500, "Bonus":0 } ] }

View File

@@ -1,24 +0,0 @@
<Window x:Class="EliteBGS.AdjustProfitWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:EliteBGS"
mc:Ignorable="d"
Title="Adjust Trade Profit" Height="130" Width="450">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Label Content="Use this dialog to adjust trade profits" Grid.Row="0" Grid.ColumnSpan="2" />
<TextBox x:Name="Profit" Grid.Row="1" Grid.ColumnSpan="2" Margin="10,10,10,10"/>
<Button x:Name="Cancel" Content="Cancel" Width="60" Grid.Row="2" Grid.Column="0" HorizontalAlignment="Right" Margin="5,0,5,0" IsCancel="true" Click="Cancel_Click"/>
<Button x:Name="Accept" Content="Accept" Width="60" Grid.Row="2" Grid.Column="1" HorizontalAlignment="Right" Margin="5,0,5,0" IsDefault="true" Click="Accept_Click" />
</Grid>
</Window>

View File

@@ -1,34 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
namespace EliteBGS {
/// <summary>
/// Interaction logic for AdjustProfitWindow.xaml
/// </summary>
public partial class AdjustProfitWindow : Window {
public AdjustProfitWindow() {
InitializeComponent();
}
private void Cancel_Click(object sender, RoutedEventArgs e) {
DialogResult = false;
Close();
}
private void Accept_Click(object sender, RoutedEventArgs e) {
DialogResult = true;
Close();
}
}
}

View File

@@ -1,109 +0,0 @@
{
"$schema": "https://schemastore.azurewebsites.net/schemas/json/sarif-2.1.0-rtm.5.json",
"version": "2.1.0",
"runs": [
{
"tool": {
"driver": {
"name": "Dependency Analysis",
"semanticVersion": "0.4.355802",
"informationUri": "https://docs.microsoft.com/en-us/dotnet/core/porting/upgrade-assistant-overview",
"rules": [
{
"id": "UA106",
"name": "PackageToBeAdded",
"fullDescription": {
"text": "Packages that need to be added in order to upgrade the project to chosen TFM"
},
"helpUri": "https://docs.microsoft.com/en-us/dotnet/core/porting/upgrade-assistant-overview"
}
]
}
},
"results": [
{
"ruleId": "UA106",
"message": {
"text": "Package Microsoft.DotNet.UpgradeAssistant.Extensions.Default.Analyzers, Version=0.4.355802 needs to be added."
},
"locations": [
{
"physicalLocation": {
"artifactLocation": {
"uri": "file:///D:/src/EDBGS/EliteBGS/EliteBGS.csproj"
},
"region": {}
}
}
]
},
{
"ruleId": "UA106",
"message": {
"text": "Package Microsoft.Windows.Compatibility, Version=7.0.0 needs to be added."
},
"locations": [
{
"physicalLocation": {
"artifactLocation": {
"uri": "file:///D:/src/EDBGS/EliteBGS/EliteBGS.csproj"
},
"region": {}
}
}
]
}
],
"columnKind": "utf16CodeUnits"
},
{
"tool": {
"driver": {
"name": "API Upgradability",
"semanticVersion": "0.4.355802",
"informationUri": "https://docs.microsoft.com/en-us/dotnet/core/porting/upgrade-assistant-overview"
}
},
"results": [],
"columnKind": "utf16CodeUnits"
},
{
"tool": {
"driver": {
"name": "Component Analysis",
"semanticVersion": "0.4.355802",
"informationUri": "https://docs.microsoft.com/en-us/dotnet/core/porting/upgrade-assistant-overview",
"rules": [
{
"id": "UA209",
"name": "Microsoft.DotNet.UpgradeAssistant.Extensions.Windows.WinformsDefaultFontUpdater",
"fullDescription": {
"text": "Default Font API Alert"
},
"helpUri": "about:blank"
}
]
}
},
"results": [
{
"ruleId": "UA209",
"message": {
"text": "Default font in Windows Forms has been changed from Microsoft Sans Serif to Seg Segoe UI, in order to change the default font use the API - Application.SetDefaultFont(Font font). For more details see here - https://devblogs.microsoft.com/dotnet/whats-new-in-windows-forms-in-net-6-0-preview-5/#application-wide-default-font."
},
"locations": [
{
"physicalLocation": {
"artifactLocation": {
"uri": "file:///D:/src/EDBGS/EliteBGS/EliteBGS.csproj"
},
"region": {}
}
}
]
}
],
"columnKind": "utf16CodeUnits"
}
]
}

View File

@@ -4,6 +4,14 @@
xmlns:local="clr-namespace:EliteBGS" xmlns:local="clr-namespace:EliteBGS"
StartupUri="MainWindow.xaml"> StartupUri="MainWindow.xaml">
<Application.Resources> <Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<!-- MahApps.Metro resource dictionaries. Make sure that all file names are Case Sensitive! -->
<ResourceDictionary Source="pack://application:,,,/MahApps.Metro;component/Styles/Controls.xaml" />
<ResourceDictionary Source="pack://application:,,,/MahApps.Metro;component/Styles/Fonts.xaml" />
<!-- Theme setting -->
<ResourceDictionary Source="pack://application:,,,/MahApps.Metro;component/Styles/Themes/Light.Blue.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources> </Application.Resources>
</Application> </Application>

View File

@@ -1,4 +1,5 @@
using System.Windows; using ControlzEx.Theming;
using System.Windows;
namespace EliteBGS { namespace EliteBGS {
/// <summary> /// <summary>

View File

@@ -1,48 +0,0 @@
<Window x:Class="EliteBGS.CombatZoneDialog"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:EliteBGS"
mc:Ignorable="d"
Title="Add Combat Zone Wins" Height="150" Width="370" Icon="EliteBGS.ico" WindowStartupLocation="CenterOwner">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<GroupBox Header="Add Combat Zone" Grid.Row="0" Grid.Column="0" Width="Auto">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<ComboBox x:Name="type" Grid.Column="0" VerticalAlignment="Top" Width="Auto" IsReadOnly="True" Height="23" Margin="5" SelectedIndex="0">
<ComboBoxItem Content="Ship"/>
<ComboBoxItem Content="On Foot"/>
</ComboBox>
<ComboBox x:Name="grade" Grid.Column="1" VerticalAlignment="Top" IsReadOnly="True" Margin="5" Height="23" Width="Auto" SelectedIndex="0">
<ComboBoxItem Content="Low"/>
<ComboBoxItem Content="Medium"/>
<ComboBoxItem Content="High"/>
</ComboBox>
<TextBox x:Name="amount" Grid.Column="2" Height="23" TextWrapping="Wrap" Text="1" VerticalAlignment="Top" Width="Auto" Margin="5" HorizontalContentAlignment="Right"/>
</Grid>
</GroupBox>
<Grid Grid.Row="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Button x:Name="Accept" Content="Accept" HorizontalAlignment="Right" Grid.Column="0" Grid.Row="1" VerticalAlignment="Top" Width="75" Margin="5" IsDefault="True" Click="Accept_Click"/>
<Button x:Name="Cancel" Content="Cancel" HorizontalAlignment="Right" Grid.Column="1" Grid.Row="1" VerticalAlignment="Top" Width="75" Margin="5" IsCancel="True" Click="Cancel_Click"/>
</Grid>
</Grid>
</Window>

View File

@@ -1,47 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Forms;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
namespace EliteBGS {
/// <summary>
/// Interaction logic for CombatZoneDialog.xaml
/// </summary>
public partial class CombatZoneDialog : Window {
public CombatZoneDialog() {
InitializeComponent();
}
public string Type => (type.SelectedItem as ComboBoxItem).Content.ToString();
public string Grade => (grade.SelectedItem as ComboBoxItem).Content.ToString();
public int Amount {
get {
try {
return int.Parse(amount.Text);
} catch (Exception) {
return 1;
}
}
}
private void Accept_Click(object sender, RoutedEventArgs e) {
DialogResult = true;
Close();
}
private void Cancel_Click(object sender, RoutedEventArgs e) {
DialogResult = false;
Close();
}
}
}

View File

@@ -3,15 +3,16 @@ using System.Linq;
using System.Collections.Generic; using System.Collections.Generic;
using System.Text; using System.Text;
using EliteBGS.LogGenerator; using EliteBGS.LogGenerator;
using System.Reflection;
namespace EliteBGS; namespace EliteBGS;
public class DiscordLogGenerator { public class DiscordLogGenerator {
protected List<LogFormatter> formatters = new List<LogFormatter>() { protected List<LogFormatter> formatters = new List<LogFormatter>() {
new MissionFormat(), new MissionFormat(),
new FailedMissionFormat(),
new MurderFormat(), new MurderFormat(),
new VoucherFormat(), new VoucherFormat(),
new ThargoidFormatter(),
new CombatZoneFormat(), new CombatZoneFormat(),
new KillBondsFormat(), new KillBondsFormat(),
new CartographicsFormat(), new CartographicsFormat(),
@@ -22,6 +23,66 @@ public class DiscordLogGenerator {
new SearchAndRescueFormat(), new SearchAndRescueFormat(),
}; };
protected virtual string GetToolVersion() {
Version v = Assembly.GetCallingAssembly().GetName().Version;
string ver;
if (v == null) {
ver = "v?.?.?";
} else {
ver = "v" + v.ToString(3);
}
return string.Format("EliteBGS {0}", ver);
}
protected virtual DateTime? GetDateOfEarliestEntry(Objective objective) {
var it = objective
.Transactions
.OrderBy(x => x.CompletedAtDateTime)
.FirstOrDefault()
;
if (it != null) {
return it.CompletedAtDateTime;
}
return null;
}
protected virtual DateTime? GetDateOfLatestEntry(Objective objective) {
var it = objective
.Transactions
.OrderByDescending(x => x.CompletedAtDateTime)
.FirstOrDefault()
;
if (it != null) {
return it.CompletedAtDateTime;
}
return null;
}
protected virtual string GenerateSummary(Objective objective) {
StringBuilder sb = new StringBuilder();
foreach (var formatter in formatters) {
string summary = "";
try {
summary = formatter.GenerateSummary(objective);
} catch (NotImplementedException) {
}
if (string.IsNullOrEmpty(summary)) {
continue;
}
if (sb.Length > 0) {
sb.Append("; ");
}
sb.Append(summary);
}
return sb.ToString();
}
protected virtual string GenerateHeader() { protected virtual string GenerateHeader() {
return ""; return "";
} }
@@ -33,10 +94,42 @@ public class DiscordLogGenerator {
protected virtual string GenerateObjectiveHeader(Objective objective) { protected virtual string GenerateObjectiveHeader(Objective objective) {
StringBuilder log = new StringBuilder(); StringBuilder log = new StringBuilder();
log.AppendFormat("**Date:** {0}\n", DateTime.Now.ToString("dd/MM/yyyy")); string location;
log.AppendFormat("**Location:** {0}, {1}\n", objective.System, objective.Faction);
log.AppendFormat("**Faction:** {0}\n", objective.Faction); if (!string.IsNullOrEmpty(objective.System) && !string.IsNullOrEmpty(objective.Faction)) {
log.AppendLine(""); location = string.Format("{0}, {1}", objective.System, objective.Faction);
} else if (!string.IsNullOrEmpty(objective.System)) {
location = objective.System;
} else {
location = "Unknown Location";
}
int legacycount = objective.Transactions
.Where(x => x.IsLegacy)
.Count()
;
string summary = GenerateSummary(objective);
log.AppendFormat("**Log Generated:** {0} by {1}\n",
DateTime.Now.ToString("dd/MM/yyyy"),
GetToolVersion()
);
var earliest = GetDateOfEarliestEntry(objective);
var latest = GetDateOfLatestEntry(objective);
if (earliest != null && latest != null) {
log.AppendFormat("**Date:** {0} - {1}\n",
GetDateOfEarliestEntry(objective),
GetDateOfLatestEntry(objective)
);
}
log.AppendFormat("**Target:** {0}\n", location);
if (!string.IsNullOrEmpty(summary)) {
log.AppendFormat("**Summary**: {0}\n", summary);
}
if (legacycount > 0) {
log.AppendFormat("**Warning:** Some actions were performed on ED Legacy\n");
}
log.AppendLine("```"); log.AppendLine("```");
return log.ToString(); return log.ToString();
@@ -46,6 +139,16 @@ public class DiscordLogGenerator {
return "```\n"; return "```\n";
} }
/// <summary>
/// Is called to do final adjustments to the log body of a given objective.
/// </summary>
/// <param name="objective">Objective in question.</param>
/// <param name="log">Final log as generated.</param>
/// <returns>The transformed log.</returns>
protected virtual string TransformFinalLogForObjective(Objective objective, string log) {
return log;
}
public virtual string GenerateDiscordLog(Report report) { public virtual string GenerateDiscordLog(Report report) {
StringBuilder log = new StringBuilder(); StringBuilder log = new StringBuilder();
@@ -61,12 +164,12 @@ public class DiscordLogGenerator {
return ""; return "";
} }
log.AppendFormat("{0}\n", GenerateHeader()); log.AppendFormat("{0}", GenerateHeader());
foreach (Objective objective in objectives) { foreach (Objective objective in objectives) {
StringBuilder objlog = new StringBuilder(); StringBuilder objlog = new StringBuilder();
log.AppendFormat("{0}\n", GenerateObjectiveHeader(objective)); log.AppendFormat("{0}", GenerateObjectiveHeader(objective));
foreach (LogFormatter formatter in formatters) { foreach (LogFormatter formatter in formatters) {
string text = formatter.GenerateLog(objective); string text = formatter.GenerateLog(objective);
@@ -76,12 +179,15 @@ public class DiscordLogGenerator {
} }
} }
log.AppendFormat("{0}\n", objlog.ToString().Trim()); string finallog = objlog.ToString().Trim();
finallog = TransformFinalLogForObjective(objective, finallog);
log.AppendFormat("{0}\n", GenerateObjectiveFooter(objective)); log.AppendFormat("{0}\n", finallog);
log.AppendFormat("{0}", GenerateObjectiveFooter(objective));
} }
log.AppendFormat("{0}\n", GenerateFooter()); log.AppendFormat("{0}", GenerateFooter());
return log.ToString().Trim(); return log.ToString().Trim();
} }

View File

@@ -0,0 +1,86 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0-windows</TargetFramework>
<OutputType>WinExe</OutputType>
<Version>0.2.6</Version>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<UseWindowsForms>true</UseWindowsForms>
<UseWPF>true</UseWPF>
<ImportWindowsDesktopTargets>true</ImportWindowsDesktopTargets>
</PropertyGroup>
<PropertyGroup>
<StartupObject>EliteBGSApplication</StartupObject>
</PropertyGroup>
<PropertyGroup>
<ApplicationIcon>Salus.ico</ApplicationIcon>
<Title>BGS reporting and logging tool for Elite:Dangerous</Title>
<Authors>nola</Authors>
<Copyright>Copyright 2019 by Florian Stinglmayr</Copyright>
<RepositoryUrl>https://git.aror.org/florian/EDBGS</RepositoryUrl>
<PackageTags>ED;Elite Dangerous;BGS</PackageTags>
<PackageProjectUrl>https://bgs.n0la.org</PackageProjectUrl>
<PackageReadmeFile>README.md</PackageReadmeFile>
</PropertyGroup>
<ItemGroup>
<Resource Include="main-page.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Resource>
<None Update="README.md">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<Pack>True</Pack>
<PackagePath>\</PackagePath>
</None>
<None Update="docs\CHANGELOG.md">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>
<Resource Include="docs\main-page.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Resource>
</ItemGroup>
<ItemGroup>
<Content Include="LICENCE.txt">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<Resource Include="Salus.ico" />
</ItemGroup>
<ItemGroup>
<Resource Include="logo_v4.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Resource>
</ItemGroup>
<ItemGroup>
<Resource Include="Resources\EliteBGS.ico" />
</ItemGroup>
<ItemGroup>
<Resource Include="EliteBGS.ico" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Extended.Wpf.Toolkit" Version="4.5.0" />
<PackageReference Include="Microsoft.CSharp" Version="4.7.0" />
<PackageReference Include="Microsoft.Windows.Compatibility" Version="7.0.0" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="13.0.2" />
<PackageReference Include="Ookii.Dialogs.Wpf" Version="5.0.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\EDPlayerJournal\EDPlayerJournal.csproj" />
</ItemGroup>
<ItemGroup>
<Compile Update="Resources.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>Resources.resx</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Update="Resources.resx">
<Generator>PublicResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
</EmbeddedResource>
</ItemGroup>
</Project>

View File

@@ -2,6 +2,7 @@
<PropertyGroup> <PropertyGroup>
<TargetFramework>net7.0-windows</TargetFramework> <TargetFramework>net7.0-windows</TargetFramework>
<OutputType>WinExe</OutputType> <OutputType>WinExe</OutputType>
<Version>0.3.5</Version>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo> <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<UseWindowsForms>true</UseWindowsForms> <UseWindowsForms>true</UseWindowsForms>
<UseWPF>true</UseWPF> <UseWPF>true</UseWPF>
@@ -11,22 +12,30 @@
<StartupObject>EliteBGSApplication</StartupObject> <StartupObject>EliteBGSApplication</StartupObject>
</PropertyGroup> </PropertyGroup>
<PropertyGroup> <PropertyGroup>
<ApplicationIcon>EliteBGS.ico</ApplicationIcon> <ApplicationIcon>Salus.ico</ApplicationIcon>
<Title>BGS reporting and logging tool for Elite:Dangerous</Title>
<Authors>nola</Authors>
<Copyright>Copyright 2019 by Florian Stinglmayr</Copyright>
<RepositoryUrl>https://codeberg.org/nola/EDBGS</RepositoryUrl>
<PackageTags>ED;Elite Dangerous;BGS</PackageTags>
<PackageProjectUrl>https://bgs.n0la.org</PackageProjectUrl>
<PackageReadmeFile>README.md</PackageReadmeFile>
<Description>Elite: Dangerous BGS logging and reporting tool
</Description>
<PackageIcon>logo_v5.png</PackageIcon>
</PropertyGroup> </PropertyGroup>
<ItemGroup>
<Reference Include="EDJournal, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\edjournal\bin\Debug\EDJournal.dll</HintPath>
</Reference>
<Reference Include="System.Design" />
<Reference Include="System.Security" />
</ItemGroup>
<ItemGroup> <ItemGroup>
<Resource Include="main-page.png"> <Resource Include="main-page.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Resource> </Resource>
<None Update="logo_v5.png">
<Pack>True</Pack>
<PackagePath>\</PackagePath>
</None>
<None Update="README.md"> <None Update="README.md">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<Pack>True</Pack>
<PackagePath>\</PackagePath>
</None> </None>
<None Update="docs\CHANGELOG.md"> <None Update="docs\CHANGELOG.md">
<CopyToOutputDirectory>Always</CopyToOutputDirectory> <CopyToOutputDirectory>Always</CopyToOutputDirectory>
@@ -43,32 +52,34 @@
</Content> </Content>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Resource Include="logo_v4.png"> <Resource Include="Salus.ico" />
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Resource>
</ItemGroup>
<ItemGroup>
<Resource Include="Resources\EliteBGS.ico" />
</ItemGroup>
<ItemGroup>
<Resource Include="EliteBGS.ico" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="MahApps.Metro" Version="2.4.9" />
<PackageReference Include="Microsoft.CSharp" Version="4.7.0" /> <PackageReference Include="Microsoft.CSharp" Version="4.7.0" />
<PackageReference Include="System.Data.DataSetExtensions" Version="4.5.0" />
<PackageReference Include="Microsoft.DotNet.UpgradeAssistant.Extensions.Default.Analyzers" Version="0.4.355802">
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Microsoft.Windows.Compatibility" Version="7.0.0" /> <PackageReference Include="Microsoft.Windows.Compatibility" Version="7.0.0" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" /> <PackageReference Include="Newtonsoft.Json" Version="13.0.2" />
<PackageReference Include="Ookii.Dialogs.Wpf" Version="5.0.1" /> <PackageReference Include="Ookii.Dialogs.Wpf" Version="5.0.1" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\EDPlayerJournal\EDPlayerJournal.csproj" /> <ProjectReference Include="..\EDPlayerJournal\EDPlayerJournal.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Folder Include="BGS\" /> <Compile Update="Resources.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>Resources.resx</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Update="Resources.resx">
<Generator>PublicResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<Folder Include="Resources\" />
</ItemGroup> </ItemGroup>
</Project> </Project>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 200 KiB

View File

@@ -6,6 +6,7 @@ using System.Windows;
using Microsoft.Win32; using Microsoft.Win32;
using EDPlayerJournal.Entries; using EDPlayerJournal.Entries;
using EliteBGS.Util; using EliteBGS.Util;
using EDPlayerJournal.BGS;
namespace EliteBGS; namespace EliteBGS;
@@ -85,6 +86,8 @@ public partial class LoadEntriesWindow : Window {
return; return;
} }
TransactionParser parser = new();
try { try {
List<Entry> entries = new List<Entry>(); List<Entry> entries = new List<Entry>();
@@ -93,8 +96,10 @@ public partial class LoadEntriesWindow : Window {
continue; continue;
} }
Entry entry = Entry.Parse(line); Entry entry = Entry.Parse(line);
if (parser.IsRelevant(entry)) {
entries.Add(entry); entries.Add(entry);
} }
}
if (entries.Count <= 0) { if (entries.Count <= 0) {
return; return;

View File

@@ -1,5 +1,6 @@
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using System.Windows.Documents;
using EDPlayerJournal; using EDPlayerJournal;
using EDPlayerJournal.BGS; using EDPlayerJournal.BGS;
@@ -53,4 +54,24 @@ public class CargoSoldFormatter : LogFormatter {
return builder.ToString(); return builder.ToString();
} }
public string GenerateSummary(Objective objective) {
SellCargo[] sold = objective.EnabledOfType<SellCargo>().ToArray();
long totalProfit = sold.Sum(x => x.Profit);
long tons = sold.Sum(x => x.Amount);
if (tons <= 0) {
return "";
}
StringBuilder builder = new();
builder.Append("Sold: ");
builder.AppendFormat("{0}t", tons);
if (totalProfit >= 100000) {
builder.AppendFormat(", {0} profit", Credits.FormatMillions(totalProfit));
}
return builder.ToString();
}
} }

View File

@@ -19,4 +19,15 @@ public class CartographicsFormat : LogFormatter {
pages, Credits.FormatCredits(sum) pages, Credits.FormatCredits(sum)
); );
} }
public string GenerateSummary(Objective objective) {
Cartographics[] sold = objective.EnabledOfType<Cartographics>().ToArray();
long totalProfit = sold.Sum(x => x.TotalSum);
if (totalProfit >= 100000) {
return string.Format("Explo: {0}", Credits.FormatMillions(totalProfit));
}
return "";
}
} }

View File

@@ -1,6 +1,100 @@
using EDPlayerJournal.BGS; using EDPlayerJournal;
using EDPlayerJournal.BGS;
using System.Linq;
using System.Text;
namespace EliteBGS.LogGenerator; namespace EliteBGS.LogGenerator;
class CombatZoneFormat : GenericFormat<CombatZone> { class CombatZoneFormat : LogFormatter {
public string GenerateLog(Objective objective) {
var logs = objective
.EnabledOfType<CombatZone>()
.OrderBy(x => (CombatZones.DifficultyRank(x.Grade) ?? 0))
.GroupBy(x => new { x.Type, x.Grade, x.Settlement })
.ToDictionary(x => x.Key, x => x.ToList())
;
StringBuilder builder = new StringBuilder();
if (logs == null || logs.Count() <= 0) {
return "";
}
foreach (var log in logs) {
int optionals = log.Value
.Sum(x => x.OptionalObjectivesCompleted)
;
var settlements = log.Value
.Select(x => x.Settlement)
.Distinct()
;
string settl = string.Join(", ", settlements);
if (!string.IsNullOrEmpty(log.Key.Grade)) {
builder.AppendFormat("Won {0}x {1} {2} Combat Zone(s)",
log.Value.Count,
log.Key.Grade,
log.Key.Type
);
} else {
builder.AppendFormat("Won {0}x {1} Combat Zone(s)",
log.Value.Count,
log.Key.Type
);
}
if (optionals > 0) {
builder.AppendFormat(" (with {0} optional objectives)", optionals);
}
if (!string.IsNullOrEmpty(settl)) {
builder.AppendFormat(" (at {0})", settl);
}
builder.Append("\n");
}
return builder.ToString().Trim();
}
public string GenerateSummary(Objective objective) {
var logs = objective
.EnabledOfType<CombatZone>()
.OrderBy(x => (CombatZones.DifficultyRank(x.Grade) ?? 0))
.GroupBy(x => new { x.Type, x.Grade })
.ToDictionary(x => x.Key, x => x.ToList())
;
StringBuilder builder = new StringBuilder();
if (logs == null || logs.Count() <= 0) {
return "";
}
foreach (var log in logs) {
int optionals = log.Value
.Sum(x => x.OptionalObjectivesCompleted)
;
if (builder.Length > 0) {
builder.Append(", ");
}
if (!string.IsNullOrEmpty(log.Key.Grade)) {
string grade = log.Key.Grade.Substring(0, 1);
if (log.Key.Grade == CombatZones.DifficultyVeryHigh) {
grade = "VH";
}
builder.AppendFormat("CZ: {0}x{1}{2}",
log.Value.Count,
grade,
log.Key.Type.Substring(0, 1)
);
} else {
builder.AppendFormat("CZ: {0}x?{1}",
log.Value.Count,
log.Key.Type.Substring(0, 1)
);
}
if (optionals > 0) {
builder.AppendFormat("+ {0} OPTS", optionals);
}
}
return builder.ToString().Trim();
}
} }

View File

@@ -1,28 +0,0 @@
using System.Linq;
using System.Text;
using EDPlayerJournal.Entries;
using EDPlayerJournal.BGS;
namespace EliteBGS.LogGenerator;
public class FailedMissionFormat : LogFormatter {
public string GenerateLog(Objective objective) {
var missions = objective.EnabledOfType<MissionFailed>();
StringBuilder builder = new StringBuilder();
if (missions.Count <= 0) {
return "";
}
foreach (MissionFailed failed in missions) {
MissionFailedEntry f = failed.Failed;
builder.AppendFormat("Failed {0} mission(s) \"{1}\" targeting {2}\n",
failed.Amount,
string.IsNullOrEmpty(f.Mission.LocalisedName) ? f.Mission.Name : f.Mission.LocalisedName,
failed.Faction
);
}
return builder.ToString().Trim();
}
}

View File

@@ -25,4 +25,8 @@ public class GenericFormat<Type> : LogFormatter where Type : Transaction {
return builder.ToString(); return builder.ToString();
} }
public virtual string GenerateSummary(Objective objective) {
throw new System.NotImplementedException();
}
} }

View File

@@ -1,6 +1,38 @@
using EDPlayerJournal.BGS; using EDPlayerJournal;
using EDPlayerJournal.BGS;
using System.Linq;
using System.Text;
namespace EliteBGS.LogGenerator; namespace EliteBGS.LogGenerator;
public class KillBondsFormat : GenericFormat<FactionKillBonds> { public class KillBondsFormat : GenericFormat<FactionKillBonds> {
public override string GenerateSummary(Objective objective) {
StringBuilder builder = new StringBuilder();
var bonds = objective
.EnabledOfType<FactionKillBonds>()
.GroupBy(x => x.VictimFaction)
.ToDictionary(x => x.Key, x => x.ToList())
;
if (bonds.Count <= 0) {
return "";
}
builder.Append("Killbonds: ");
foreach (var entry in bonds) {
long sum = (long)entry.Value.Sum(x => (decimal)x.TotalSum);
builder.AppendFormat("{0} against {1}, ",
Credits.FormatMillions(sum),
entry.Key
);
}
if (builder.Length > 2) {
// Remove trailing comma
builder.Remove(builder.Length - 2, 2);
}
return builder.ToString();
}
} }

View File

@@ -2,4 +2,5 @@
public interface LogFormatter { public interface LogFormatter {
string GenerateLog(Objective objective); string GenerateLog(Objective objective);
string GenerateSummary(Objective objective);
} }

View File

@@ -1,6 +1,17 @@
using EDPlayerJournal.BGS; using EDPlayerJournal.BGS;
using System.Linq;
namespace EliteBGS.LogGenerator; namespace EliteBGS.LogGenerator;
public class MarketBuyFormat : GenericFormat<BuyCargo> { public class MarketBuyFormat : GenericFormat<BuyCargo> {
public override string GenerateSummary(Objective objective) {
long tons = objective
.EnabledOfType<BuyCargo>()
.Sum(x => x.Amount)
;
if (tons <= 0) {
return "";
}
return string.Format("Bought: {0}t", tons);
}
} }

View File

@@ -16,4 +16,8 @@ public class MicroResourcesFormat : LogFormatter {
return string.Format("Sold {0} worth of Micro Resources\n", return string.Format("Sold {0} worth of Micro Resources\n",
Credits.FormatCredits(sum)); Credits.FormatCredits(sum));
} }
public string GenerateSummary(Objective objective) {
return "";
}
} }

View File

@@ -6,14 +6,72 @@ using EDPlayerJournal.BGS;
namespace EliteBGS.LogGenerator; namespace EliteBGS.LogGenerator;
public class MissionFormat : LogFormatter { public class MissionFormat : LogFormatter {
private string GenerateFailedLog(Objective objective) {
var missions = objective.EnabledOfType<MissionFailed>();
if (missions.Count <= 0) {
return "";
}
StringBuilder builder = new StringBuilder();
var grouping = missions
.GroupBy(x => x.Mission.IsOnFoot)
;
foreach (var group in grouping) {
int amount = group.Count();
if (group.Key) {
builder.AppendFormat("Failed {0} On Foot Mission(s)\n", amount);
} else {
builder.AppendFormat("Failed {0} Ship Mission(s)\n", amount);
}
}
return builder.ToString().Trim();
}
private string GenerateFailedSummary(Objective objective) {
var missions = objective.EnabledOfType<MissionFailed>();
if (missions.Count <= 0) {
return "";
}
StringBuilder sb = new();
int onFootFails = missions.Where(x => x.Mission.IsOnFoot).Count();
int shipFails = missions.Where(x => !x.Mission.IsOnFoot).Count();
sb.Append("Fails: ");
if (onFootFails > 0) {
sb.AppendFormat("{0} Ground", onFootFails);
}
if (shipFails > 0) {
if (onFootFails > 0) {
sb.Append(", ");
}
sb.AppendFormat("{0} Ship", shipFails);
}
return sb.ToString();
}
public string GenerateLog(Objective objective) { public string GenerateLog(Objective objective) {
Dictionary<string, Dictionary<string, int>> collated = new Dictionary<string, Dictionary<string, int>>(); Dictionary<string, Dictionary<string, int>> collated = new();
Dictionary<string, ulong> passengers = new();
StringBuilder output = new StringBuilder(); StringBuilder output = new StringBuilder();
int total_influence = 0; long total_influence = 0;
var missions = objective.EnabledOfType<MissionCompleted>(); var missions = objective.EnabledOfType<MissionCompleted>();
var support = objective.EnabledOfType<InfluenceSupport>();
var failed = objective.EnabledOfType<MissionFailed>();
if (missions == null || missions.Count == 0) { if ((missions == null || missions.Count == 0) &&
(support == null || support.Count == 0) &&
(failed == null || failed.Count == 0)) {
return ""; return "";
} }
@@ -28,37 +86,85 @@ public class MissionFormat : LogFormatter {
++collated[m.MissionName][m.Influence]; ++collated[m.MissionName][m.Influence];
total_influence += m.Influence.Length; total_influence += m.Influence.Length;
if (m.AcceptedEntry != null &&
m.AcceptedEntry.Mission != null &&
m.AcceptedEntry.Mission.IsPassengerMission) {
if (!passengers.ContainsKey(m.MissionName)) {
passengers[m.MissionName] = 0;
}
passengers[m.MissionName] += (m.AcceptedEntry.Mission.PassengerCount ?? 0);
}
} }
foreach (var mission in collated) { foreach (var mission in collated) {
if (objective.Faction != null) { output.AppendFormat("{0}: ", mission.Key);
output.AppendFormat("{0}\n", mission.Key);
} else {
output.AppendFormat("{0}\n", mission.Key);
}
output.Append("("); output.Append("(");
foreach (var influence in mission.Value.OrderBy(x => x.Key.Length)) { foreach (var influence in mission.Value.OrderBy(x => x.Key.Length)) {
output.AppendFormat("Inf{0} x{1}, ", influence.Key, influence.Value); output.AppendFormat("Inf{0} x{1}, ", influence.Key, influence.Value);
} }
output.Remove(output.Length - 2, 2); // remove last ", " output.Remove(output.Length - 2, 2); // remove last ", "
output.Append(")\n\n"); output.Append(")");
if (passengers.ContainsKey(mission.Key)) {
output.AppendFormat(" ({0} Passengers)", passengers[mission.Key]);
} }
var support = objective.EnabledOfType<InfluenceSupport>(); output.Append("\n");
}
output.Append("\n");
// Handle failed missions, and add them to the log and influence tally
string failedlog = GenerateFailedLog(objective);
if (!string.IsNullOrEmpty(failedlog)) {
output.Append(failedlog);
output.Append("\n");
}
total_influence += failed.Sum(x => x.InfluenceAmount);
foreach (InfluenceSupport inf in support) { foreach (InfluenceSupport inf in support) {
output.Append(inf.ToString()); output.Append(inf.ToString());
output.Append("\n"); output.Append("\n");
total_influence += inf.Influence.Length; total_influence += inf.Influence.InfluenceAmount;
} }
if (support.Count() > 0) { if (support.Count() > 0) {
output.Append("\n"); output.Append("\n");
} }
if (total_influence > 0) { if (total_influence != 0) {
output.AppendFormat("Total Influence: {0}", total_influence); output.AppendFormat("Total Influence: {0}", total_influence);
} }
return output.ToString().Trim(); return output.ToString().Trim();
} }
public string GenerateSummary(Objective objective) {
long influence = objective
.EnabledOfType<MissionCompleted>()
.Sum(x => x.Influence.Length)
;
long support = objective
.EnabledOfType<InfluenceSupport>()
.Sum(x => x.Influence.InfluenceAmount)
;
long failed = objective
.EnabledOfType<MissionFailed>()
.Sum(x => x.InfluenceAmount)
;
if (influence == 0 && support == 0 && failed == 0) {
return "";
}
string failedsummary = GenerateFailedSummary(objective);
string summary = string.Format("INF: {0}", influence + support + failed);
if (!string.IsNullOrEmpty(failedsummary)) {
string.Join("; ", summary, failedsummary);
}
return summary;
}
} }

View File

@@ -1,6 +1,60 @@
using EDPlayerJournal.BGS; using EDPlayerJournal;
using EDPlayerJournal.BGS;
using System.Text;
using System.Linq;
namespace EliteBGS.LogGenerator; namespace EliteBGS.LogGenerator;
public class MurderFormat : GenericFormat<FoulMurder> { public class MurderFormat : LogFormatter {
public string GenerateLog(Objective objective) {
var logs = objective
.EnabledOfType<FoulMurder>()
.GroupBy(x => x.CrimeType)
.ToDictionary(x => x.Key, x => x.ToList())
;
StringBuilder builder = new StringBuilder();
if (logs == null || logs.Count() <= 0) {
return "";
}
foreach (var log in logs) {
string type;
if (string.Compare(log.Key, CrimeTypes.Murder) == 0) {
if (log.Value.Count > 1) {
type = "ships";
} else {
type = "ship";
}
} else {
if (log.Value.Count > 1) {
type = "people";
} else {
type = "person";
}
}
long bounties = log.Value.Sum(x => x.Bounties);
builder.AppendFormat("Murdered {0} {1} (Bounties: {2})\n",
log.Value.Count, type,
Credits.FormatMillions(bounties)
);
}
return builder.ToString().Trim();
}
public string GenerateSummary(Objective objective) {
long murders = objective
.EnabledOfType<FoulMurder>()
.Where(x => x.CrimeType == CrimeTypes.Murder || x.CrimeType == CrimeTypes.OnFootMurder)
.Count()
;
if (murders <= 0) {
return "";
}
return string.Format("Kills: {0}", murders);
}
} }

View File

@@ -1,6 +1,22 @@
using EDPlayerJournal.BGS; using EDPlayerJournal;
using EDPlayerJournal.BGS;
using System.Linq;
namespace EliteBGS.LogGenerator; namespace EliteBGS.LogGenerator;
public class SearchAndRescueFormat : GenericFormat<SearchAndRescue> { public class SearchAndRescueFormat : GenericFormat<SearchAndRescue> {
public override string GenerateSummary(Objective objective) {
long tons = objective
.EnabledOfType<SearchAndRescue>()
.Sum(x => x.Count)
;
long profit = objective
.EnabledOfType<SearchAndRescue>()
.Sum(x => x.Reward)
;
if (tons <= 0) {
return "";
}
return string.Format("S&R: {0}t, {1} profit", tons, Credits.FormatMillions(profit));
}
} }

View File

@@ -0,0 +1,65 @@
using EDPlayerJournal;
using EDPlayerJournal.BGS;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace EliteBGS.LogGenerator;
public class ThargoidFormatter : LogFormatter {
public string GenerateLog(Objective objective) {
List<ThargoidKill> kills = objective.EnabledOfType<ThargoidKill>().ToList();
if (kills.Count == 0 ) {
return "";
}
Dictionary<ThargoidVessel, List<ThargoidKill>> sorted = kills
.GroupBy(x => x.ThargoidType)
.ToDictionary(x => x.Key, x => x.ToList())
;
StringBuilder builder = new StringBuilder();
foreach (var k in sorted) {
string name = Thargoid.GetVesselName(k.Key);
builder.AppendFormat("{0}x {1}(s) killed\n", k.Value.Count, name);
}
return builder.ToString();
}
public string GenerateSummary(Objective objective) {
List<ThargoidKill> kills = objective.EnabledOfType<ThargoidKill>().ToList();
if (kills.Count == 0 ) {
return "";
}
int drones = kills.Where(x => x.ThargoidType == ThargoidVessel.Revenant).Count();
int scouts = kills.Where(x => x.ThargoidType == ThargoidVessel.Scout).Count();
int interceptors = kills.Count - scouts - drones;
StringBuilder builder = new StringBuilder();
builder.Append("AX: ");
if (interceptors > 0) {
builder.AppendFormat("{0} INT", interceptors);
}
if (scouts > 0) {
if (interceptors > 0) {
builder.Append(", ");
}
builder.AppendFormat("{0} SCT", scouts);
}
if (drones > 0) {
if (interceptors > 0 || scouts > 0) {
builder.Append(", ");
}
builder.AppendFormat("{0} DRN", drones);
}
return builder.ToString();
}
}

View File

@@ -1,6 +1,18 @@
using EDPlayerJournal.BGS; using EDPlayerJournal;
using EDPlayerJournal.BGS;
using System.Linq;
namespace EliteBGS.LogGenerator; namespace EliteBGS.LogGenerator;
class VistaGenomicsFormat : GenericFormat<OrganicData> { class VistaGenomicsFormat : GenericFormat<OrganicData> {
public override string GenerateSummary(Objective objective) {
long profit = objective
.EnabledOfType<OrganicData>()
.Sum(x => x.TotalValue)
;
if (profit <= 0) {
return "";
}
return string.Format("Organic: {0} Profit", Credits.FormatMillions(profit));
}
} }

View File

@@ -8,10 +8,10 @@ namespace EliteBGS.LogGenerator;
public class VoucherFormat : LogFormatter { public class VoucherFormat : LogFormatter {
public string GenerateLog(Objective objective) { public string GenerateLog(Objective objective) {
StringBuilder builder = new StringBuilder(); StringBuilder builder = new StringBuilder();
var missions = objective.UITransactions var missions = objective
.Where(x => x.IsEnabled) .EnabledOfType<Vouchers>()
.Select(x => x.Transaction) .GroupBy(x => x.Type)
.OfType<Vouchers>() .ToDictionary(x => x.Key, x => x.ToList())
; ;
if (missions == null || missions.Count() <= 0) { if (missions == null || missions.Count() <= 0) {
@@ -19,11 +19,38 @@ public class VoucherFormat : LogFormatter {
} }
foreach (var m in missions) { foreach (var m in missions) {
builder.AppendFormat("Handed in {0} vouchers for {1}\n", m.Type, m.Faction); long total = (long)m.Value.Sum(x => (decimal)x.TotalSum);
builder.AppendFormat("(Total value: {0})\n", Credits.FormatCredits(m.TotalSum)); builder.AppendFormat("Handed in {0} vouchers: {1}\n", m.Key, Credits.FormatMillions(total));
builder.AppendFormat("\n");
} }
return builder.ToString().Trim(); return builder.ToString().Trim();
} }
public string GenerateSummary(Objective objective) {
long bounties = objective
.EnabledOfType<Vouchers>()
.Where(x => x.Type == "Bounty")
.Sum(x => x.TotalSum)
;
long bonds = objective
.EnabledOfType<Vouchers>()
.Where(x => x.Type == "Combat Bond")
.Sum(x => x.TotalSum)
;
StringBuilder sb = new();
if (bounties > 0) {
sb.AppendFormat("Bounties: {0}", Credits.FormatMillions(bounties));
}
if (bonds > 0) {
if (sb.Length > 0) {
sb.Append(", ");
}
sb.AppendFormat("Bonds: {0}", Credits.FormatMillions(bonds));
}
return sb.ToString();
}
} }

View File

@@ -1,13 +1,30 @@
<Window <mah:MetroWindow
xmlns:mah="clr-namespace:MahApps.Metro.Controls;assembly=MahApps.Metro"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:abc="http://wpfcontrols.com/"
xmlns:local="clr-namespace:EliteBGS" xmlns:local="clr-namespace:EliteBGS"
xmlns:Util="clr-namespace:EliteBGS.Util" d:DataContext="{d:DesignInstance Type=Util:AppConfig}" x:Name="window" x:Class="EliteBGS.MainWindow" xmlns:Util="clr-namespace:EliteBGS.Util" d:DataContext="{d:DesignInstance Type=Util:AppConfig}" x:Name="window" x:Class="EliteBGS.MainWindow"
mc:Ignorable="d" mc:Ignorable="d"
Title="Elite: Dangerous BGS Helper" Height="520" Width="890" Icon="EliteBGS.ico" Closing="window_Closing"> Title="Elite: Dangerous BGS Helper" Height="520" Width="950" Icon="Salus.ico" Closing="window_Closing">
<Window.Resources>
<Style x:Key="StretchingTreeViewStyle" TargetType="TreeViewItem" BasedOn="{StaticResource {x:Type TreeViewItem}}">
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
</Style>
<local:MinusFortyFiveConverter x:Key="MinusFortyFiveConverter" />
</Window.Resources>
<mah:MetroWindow.RightWindowCommands>
<mah:WindowCommands ShowSeparators="False">
<TextBlock HorizontalAlignment="Center" VerticalAlignment="Center" Margin="5,0,5,0">
<Hyperlink x:Name="URL" NavigateUri="https://bgs.n0la.org/" RequestNavigate="URL_RequestNavigate">Homepage</Hyperlink>
</TextBlock>
<TextBlock HorizontalAlignment="Center" VerticalAlignment="Center" Margin="5,0,15,0">
<Hyperlink x:Name="SRC" NavigateUri="https://codeberg.org/nola/EDBGS" RequestNavigate="URL_RequestNavigate">Source</Hyperlink>
</TextBlock>
<mah:ToggleSwitch Content="Dark Theme" x:Name="SwitchTheme" IsOn="True" Toggled="SwitchTheme_Toggled"/>
</mah:WindowCommands>
</mah:MetroWindow.RightWindowCommands>
<Grid> <Grid>
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/> <ColumnDefinition Width="*"/>
@@ -15,51 +32,122 @@
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="*"/> <RowDefinition Height="*"/>
</Grid.RowDefinitions> </Grid.RowDefinitions>
<TabControl> <TabControl Style="{DynamicResource MahApps.Styles.TabControl.Animated}">
<TabItem Header="Current Objectives"> <TabItem Header="Current Objectives">
<Grid Background="#FFE5E5E5"> <Grid>
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/>
<RowDefinition Height="*"/> <RowDefinition/>
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto"/>
<RowDefinition Height="*" /> <RowDefinition/>
</Grid.RowDefinitions> </Grid.RowDefinitions>
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/> <ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/> <ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/> <ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<ToolBar VerticalAlignment="Top" Grid.Row="1" Width="Auto" Margin="0,0,0,0" Height="Auto" Grid.ColumnSpan="3" HorizontalAlignment="Left"> <ToolBar VerticalAlignment="Top" Grid.Row="0" Width="Auto" Margin="0,0,0,0" Height="Auto" Grid.ColumnSpan="3" HorizontalAlignment="Left">
<Button x:Name="ParseJournal" Content="Parse Journal" VerticalAlignment="Center" Click="ParseJournal_Click" HorizontalAlignment="Center"/> <Button x:Name="ParseJournal" Content="Parse Journal" VerticalAlignment="Center" Click="ParseJournal_Click" HorizontalAlignment="Center"/>
<Separator Margin="1" VerticalAlignment="Center" MinWidth="1" HorizontalAlignment="Center" MinHeight="22"/> <Separator Margin="1" VerticalAlignment="Center" MinWidth="1" HorizontalAlignment="Center" MinHeight="22"/>
<Label Content="From:" VerticalAlignment="Center" VerticalContentAlignment="Center" HorizontalAlignment="Center"/> <Label Content="From (UTC):" VerticalAlignment="Center" VerticalContentAlignment="Center" HorizontalAlignment="Center"/>
<DatePicker x:Name="startdate" Height="26.2857142857143" VerticalAlignment="Center" HorizontalAlignment="Center"/> <mah:DateTimePicker x:Name="startdate" Height="26.2857142857143" VerticalAlignment="Center" HorizontalAlignment="Center"/>
<Label Content="To:" Height="26.2857142857143" VerticalAlignment="Top"/> <Label Content="To (UTC):" Height="26.2857142857143" VerticalAlignment="Center" HorizontalAlignment="Center"/>
<DatePicker x:Name="enddate" Height="26.2857142857143" VerticalAlignment="Center" HorizontalAlignment="Center"/> <mah:DateTimePicker x:Name="enddate" Height="26.2857142857143" VerticalAlignment="Center" HorizontalAlignment="Center"/>
<Separator Margin="1" VerticalAlignment="Center" MinWidth="1" HorizontalAlignment="Center" MinHeight="22"/> <Separator Margin="1" VerticalAlignment="Center" MinWidth="1" HorizontalAlignment="Center" MinHeight="22"/>
<CheckBox x:Name="collate" Margin="1" Content="Collate entries" IsChecked="True" IsThreeState="False"/> <Button x:Name="ResetTime" Content="Reset Time" Click="ResetTime_Click" />
<Separator Height="26.2857142857143" Margin="0" VerticalAlignment="Top"/>
<Button x:Name="AddCombatZone" Content="Add Combat Zone Win" VerticalAlignment="Stretch" Margin="0,0,0,0.286" RenderTransformOrigin="0.5,0.505" Click="AddCombatZone_Click"/>
<Separator Height="26.2857142857143" Margin="0" VerticalAlignment="Top"/>
<Button x:Name="AdjustProfit" Content="Adjust Trade Profit" Margin="0" VerticalAlignment="Stretch" Click="AdjustProfit_Click" />
<Separator Margin="1" VerticalAlignment="Center" MinWidth="1" HorizontalAlignment="Center" MinHeight="22"/> <Separator Margin="1" VerticalAlignment="Center" MinWidth="1" HorizontalAlignment="Center" MinHeight="22"/>
<Button x:Name="ManuallyParse" Content="Manually Parse JSON" Click="ManuallyParse_Click" /> <Button x:Name="ManuallyParse" Content="Manually Parse" Click="ManuallyParse_Click" />
</ToolBar> </ToolBar>
<TreeView CheckBox.Checked="TreeView_CheckBox_Updated" CheckBox.Unchecked="TreeView_CheckBox_Updated" x:Name="entries" Margin="0,0,0,0" Grid.ColumnSpan="3" Grid.Row="2" KeyUp="entries_KeyUp"> <ToolBar Grid.Row="1" HorizontalAlignment="Left" Height="Auto" VerticalAlignment="Top" Width="Auto" Grid.ColumnSpan="3">
<Button x:Name="GenerateDiscord" Content="Generate Discord Report" VerticalAlignment="Stretch" Margin="0,0,0,0" VerticalContentAlignment="Center" Click="GenerateDiscord_Click"/>
<Separator />
<ComboBox x:Name="LogType" VerticalAlignment="Stretch" Margin="0,3,0,3" Width="140" SelectionChanged="LogType_SelectionChanged" />
</ToolBar>
<TreeView CheckBox.Checked="TreeView_CheckBox_Updated"
CheckBox.Unchecked="TreeView_CheckBox_Updated"
x:Name="entries" Margin="0,0,0,0"
Grid.ColumnSpan="3" Grid.Row="2"
KeyUp="entries_KeyUp"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Stretch"
>
<TreeView.ItemTemplate> <TreeView.ItemTemplate>
<HierarchicalDataTemplate DataType="{x:Type local:Objective}" ItemsSource="{Binding UITransactions}"> <HierarchicalDataTemplate DataType="{x:Type local:Objective}" ItemsSource="{Binding UITransactions}" ItemContainerStyle="{StaticResource StretchingTreeViewStyle}">
<StackPanel Orientation="Horizontal"> <Grid
HorizontalAlignment="Stretch"
Width="{Binding ActualWidth, ElementName=entries, Converter={StaticResource MinusFortyFiveConverter}}"
>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0" Orientation="Horizontal" VerticalAlignment="Center" Margin="0,2,0,2">
<CheckBox Focusable="False" IsChecked="{Binding IsEnabled}" VerticalAlignment="Center"/> <CheckBox Focusable="False" IsChecked="{Binding IsEnabled}" VerticalAlignment="Center"/>
<TextBlock Text="{Binding Name}" Margin="5,0" /> <TextBlock Text="System: " Visibility="{Binding HasSystem}" Margin="2,0,0,0"/>
<TextBlock Text="{Binding System}" FontWeight="DemiBold" Visibility="{Binding HasSystem}"/>
<TextBlock Text="Faction: " Visibility="{Binding HasFaction}" Margin="2,0,0,0"/>
<TextBlock Text="{Binding Faction}" FontWeight="DemiBold" Visibility="{Binding HasFaction}"/>
</StackPanel> </StackPanel>
<Separator Visibility="Hidden" Grid.Column="1" HorizontalAlignment="Stretch" HorizontalContentAlignment="Stretch" />
<StackPanel Grid.Column="2" HorizontalAlignment="Right" VerticalAlignment="Center" Orientation="Horizontal">
<ToggleButton x:Name="ToggleAll" Content="Toggle All" Click="ToggleAll_Click" IsChecked="True" IsThreeState="False"/>
<Separator Margin="2,0,2,0" />
<Button x:Name="AddCombatZone" Content="Add Combat Zone" Click="AddCombatZone_Click" />
</StackPanel>
</Grid>
<HierarchicalDataTemplate.ItemTemplate> <HierarchicalDataTemplate.ItemTemplate>
<HierarchicalDataTemplate> <HierarchicalDataTemplate>
<StackPanel Orientation="Horizontal"> <!-- This will stretch out the width of the item-->
<Grid Initialized="Transaction_Initialized"
HorizontalAlignment="Left"
Width="{Binding ActualWidth, ElementName=entries, Converter={StaticResource MinusFortyFiveConverter}}"
Margin="0,2,0,2"
>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0" Orientation="Horizontal" HorizontalAlignment="Stretch" VerticalAlignment="Center">
<CheckBox Focusable="False" IsChecked="{Binding IsEnabled}" VerticalAlignment="Center"/> <CheckBox Focusable="False" IsChecked="{Binding IsEnabled}" VerticalAlignment="Center"/>
<TextBlock Text="{Binding CompletedAt}" Margin="5,0,5,0" HorizontalAlignment="Right"/> <TextBlock Text="{Binding CompletedAt}" Margin="2,0,2,0" HorizontalAlignment="Right" TextAlignment="Center"/>
<TextBlock Text="{Binding Name}" FontWeight="DemiBold"/> <TextBlock Text="{Binding Name}" FontWeight="DemiBold" TextAlignment="Center"/>
</StackPanel> </StackPanel>
<StackPanel Grid.Column="2" Orientation="Horizontal" HorizontalAlignment="Right" x:Name="CombatZone" Visibility="{Binding IsCombatZone}">
<Expander Header="Optional Objectives" Visibility="{Binding IsShipCombatZone}">
<StackPanel Orientation="Vertical">
<ToggleButton x:Name="CapitalShip" Margin="2,0,2,0" Content="Capital Ship" IsChecked="{Binding HasCapitalShip, Mode=TwoWay}" IsThreeState="False"/>
<ToggleButton x:Name="AlliedCaptain" Margin="2,0,2,0" Content="Allied Captain" IsChecked="{Binding HasAlliedCaptain, Mode=TwoWay}" IsThreeState="False"/>
<ToggleButton x:Name="EnemyCaptain" Margin="2,0,2,0" Content="Enemy Captain" IsChecked="{Binding HasEnemyCaptain, Mode=TwoWay}" IsThreeState="False"/>
<ToggleButton x:Name="AlliedCorrespondent" Margin="2,0,2,0" Content="Allied Correspondent" IsChecked="{Binding HasAlliedCorrespondent, Mode=TwoWay}" IsThreeState="False"/>
<ToggleButton x:Name="EnemyCorrespondent" Margin="2,0,2,0" Content="Enemy Correspondent" IsChecked="{Binding HasEnemyCorrespondent, Mode=TwoWay}" IsThreeState="False"/>
<ToggleButton x:Name="SpecOps" Margin="2,0,2,0" Content="Spec Ops" IsChecked="{Binding HasSpecOps, Mode=TwoWay}" IsThreeState="False"/>
</StackPanel>
</Expander>
<Expander Header="Difficulty">
<StackPanel Orientation="Vertical">
<Button x:Name="Low" Content="Low" Click="Low_Click"/>
<Button x:Name="Med" Content="Med" Click="Med_Click"/>
<Button x:Name="High" Content="High" Click="High_Click"/>
<Button x:Name="VeryHigh" Content="Very High" Click="VeryHigh_Click" />
</StackPanel>
</Expander>
<Expander Header="Type">
<StackPanel Orientation="Vertical">
<Button Content="Ground" x:Name="Ground" Click="Ground_Click"/>
<Button Content="Ship" x:Name="Ship" Click="Ship_Click"/>
<Button Content="AX" x:Name="Thargoid" Click="Thargoid_Click"/>
</StackPanel>
</Expander>
</StackPanel>
<StackPanel Grid.Column="2" Orientation="Horizontal" HorizontalAlignment="Right" x:Name="SellCargo" Visibility="{Binding IsSellCargo}">
<TextBlock Text="Adjust Profit: " TextAlignment="Center" />
<TextBox x:Name="Profit" MinWidth="80" HorizontalContentAlignment="Right" Text="{Binding Profit, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" LostFocus="Profit_LostFocus" KeyUp="Profit_KeyUp"/>
</StackPanel>
</Grid>
</HierarchicalDataTemplate> </HierarchicalDataTemplate>
</HierarchicalDataTemplate.ItemTemplate> </HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate> </HierarchicalDataTemplate>
@@ -70,17 +158,14 @@
</Style> </Style>
</TreeView.ItemContainerStyle> </TreeView.ItemContainerStyle>
</TreeView> </TreeView>
<ToolBar HorizontalAlignment="Left" Height="36" VerticalAlignment="Top" Width="Auto" Grid.Row="3" Grid.ColumnSpan="2"> <GridSplitter Background="LightSkyBlue" Grid.Row="3" Height="5" HorizontalAlignment="Stretch" VerticalAlignment="Top" Grid.ColumnSpan="3" ResizeBehavior="PreviousAndNext"/>
<Button x:Name="GenerateDiscord" Content="Generate Discord Report" VerticalAlignment="Center" Margin="0,0,0,4.857" Click="GenerateDiscord_Click" Height="26"/> <TextBox x:Name="DiscordLog" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Grid.Row="4" Height="Auto" TextWrapping="Wrap" FontFamily="Consolas" FontSize="14" Grid.ColumnSpan="3" AcceptsReturn="True" AcceptsTab="True"/>
<Separator />
<ComboBox x:Name="LogType" Height="36" Margin="0" VerticalAlignment="Center" Width="140" SelectionChanged="LogType_SelectionChanged" />
</ToolBar>
<TextBox x:Name="DiscordLog" Height="Auto" TextWrapping="Wrap" FontFamily="Consolas" FontSize="14" Grid.Row="4" Grid.ColumnSpan="3" AcceptsReturn="True" AcceptsTab="True"/>
</Grid> </Grid>
</TabItem> </TabItem>
<TabItem Header="Settings" HorizontalAlignment="Left" Height="20" VerticalAlignment="Top" Width="53.7142857142857"> <TabItem Header="Settings">
<Grid Background="#FFE5E5E5"> <Grid>
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/>
<RowDefinition Height="*"/> <RowDefinition Height="*"/>
@@ -99,16 +184,49 @@
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/> <ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/> <ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<Label Content="Location on disk for the player journal. There is usually no need to change this setting." Grid.Row="0" Grid.ColumnSpan="2" HorizontalAlignment="Left" Margin="0,0,0,0" VerticalAlignment="Top" RenderTransformOrigin="-0.08,0.496"/> <Label Content="Location on disk for the player journal. There is usually no need to change this setting." Grid.Row="0" Grid.ColumnSpan="2" HorizontalAlignment="Left" Margin="0,0,0,0" VerticalAlignment="Top" RenderTransformOrigin="-0.08,0.496"/>
<TextBox x:Name="journallocation" IsReadOnly="true" Text="" Grid.Row="1" Grid.Column="0" Margin="5,0,5,10" TextWrapping="Wrap" /> <TextBox x:Name="journallocation" IsReadOnly="true" Text="" Grid.Row="1" Grid.Column="0" Margin="5,0,5,10" TextWrapping="Wrap" />
<Button x:Name="browsejournallocation" Content="Browse" Grid.Row="1" Grid.Column="1" Margin="0,0,0,0" Width="Auto" VerticalAlignment="Top" HorizontalAlignment="Left" Click="browsejournallocation_Click"/> <Button x:Name="browsejournallocation" Content="Browse" Grid.Row="1" Grid.Column="1" Margin="0,0,0,0" Width="Auto" VerticalAlignment="Top" HorizontalAlignment="Left" Click="browsejournallocation_Click"/>
<Button x:Name="OpenInExplorer" Content="Open Folder" Grid.Row="1" Grid.Column="2" Margin="5,0,0,0" Width="Auto" VerticalAlignment="Top" HorizontalAlignment="Left" Click="OpenInExplorer_Click" />
</Grid>
</GroupBox>
<GroupBox Header="Theme Colour" Height="Auto" Grid.Row="1" VerticalAlignment="Top" Width="Auto" Grid.ColumnSpan="3" Margin="0,5,0,0">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Label Content="Colour of the current theme." Grid.Row="0" Grid.ColumnSpan="2" HorizontalAlignment="Left" Margin="0,0,0,0" VerticalAlignment="Top"/>
<ComboBox x:Name="Colour" IsEditable="False" Grid.Row="1" Grid.Column="0" VerticalAlignment="Center" Width="Auto" SelectionChanged="Colour_SelectionChanged"/>
</Grid>
</GroupBox>
<GroupBox Header="Advanced Parsing Options" Grid.Row="2" VerticalAlignment="Top" Width="Auto" Grid.ColumnSpan="3" Margin="0,5,0,0">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<mah:ToggleSwitch x:Name="NoInfluenceSupport" Grid.Row="0" Grid.ColumnSpan="2" Content="Ignore secondary influence support given out by certain missions" Toggled="NoInfluenceSupport_Toggled"/>
<mah:ToggleSwitch x:Name="NoMarketBuy" Grid.Row="1" Grid.ColumnSpan="2" Content="Ignore commodities bought at stations" Toggled="NoMarketBuy_Toggled"/>
<mah:ToggleSwitch x:Name="NoFleetCarrier" Grid.Row="2" Grid.ColumnSpan="2" Content="Ignore transactions done on a Fleet Carrier" Toggled="NoFleetCarrier_Toggled" />
</Grid> </Grid>
</GroupBox> </GroupBox>
</Grid> </Grid>
</TabItem> </TabItem>
<TabItem Header="Event Log" HorizontalAlignment="Left" Height="20" VerticalAlignment="Top"> <TabItem Header="Event Log">
<Grid Background="#FFE5E5E5"> <Grid>
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/> <ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
@@ -116,8 +234,8 @@
<RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/>
<RowDefinition Height="*"/> <RowDefinition Height="*"/>
</Grid.RowDefinitions> </Grid.RowDefinitions>
<TextBox IsReadOnly="True" Grid.Row="1" x:Name="log" Height="Auto" Margin="5" TextWrapping="Wrap" FontFamily="Courier New" Background="{x:Null}"/> <TextBox IsReadOnly="True" Grid.Row="1" x:Name="log" Height="Auto" Margin="5" TextWrapping="Wrap" FontFamily="Courier New" />
<RichTextBox IsReadOnly="True" HorizontalAlignment="Left" Height="Auto" Margin="5" Width="Auto" VerticalContentAlignment="Stretch" VerticalAlignment="Top" Background="{x:Null}" BorderBrush="{x:Null}" SelectionBrush="{x:Null}"> <RichTextBox IsReadOnly="True" HorizontalAlignment="Left" Height="Auto" Margin="5" Width="Auto" VerticalContentAlignment="Stretch" VerticalAlignment="Top" BorderBrush="{x:Null}" SelectionBrush="{x:Null}">
<FlowDocument> <FlowDocument>
<Paragraph> <Paragraph>
<Run Text="This tool does not recognise every option and/or configuration that E:D outputs through its JSON files."/> <Run Text="This tool does not recognise every option and/or configuration that E:D outputs through its JSON files."/>
@@ -131,4 +249,4 @@
</TabItem> </TabItem>
</TabControl> </TabControl>
</Grid> </Grid>
</Window> </mah:MetroWindow>

View File

@@ -11,13 +11,19 @@ using EDPlayerJournal.BGS;
using EDPlayerJournal.Entries; using EDPlayerJournal.Entries;
using EliteBGS.BGS; using EliteBGS.BGS;
using EliteBGS.Util; using EliteBGS.Util;
using System.Globalization;
using System.Windows.Controls.Primitives;
using MahApps.Metro.Controls;
using ControlzEx.Theming;
using System.Windows.Media;
using System.Diagnostics;
namespace EliteBGS; namespace EliteBGS;
/// <summary> /// <summary>
/// Interaction logic for MainWindow.xaml /// Interaction logic for MainWindow.xaml
/// </summary> /// </summary>
public partial class MainWindow : Window { public partial class MainWindow : MetroWindow {
private PlayerJournal journal; private PlayerJournal journal;
private Report report; private Report report;
@@ -28,6 +34,7 @@ public partial class MainWindow : Window {
private static readonly List<DiscordLogGenerator> logtypes = new List<DiscordLogGenerator>() { private static readonly List<DiscordLogGenerator> logtypes = new List<DiscordLogGenerator>() {
new NonaDiscordLog(), new NonaDiscordLog(),
new GenericDiscordLog(), new GenericDiscordLog(),
new OneLineDiscordLog(),
}; };
public MainWindow() { public MainWindow() {
@@ -51,32 +58,100 @@ public partial class MainWindow : Window {
LogType.SelectedIndex = 0; LogType.SelectedIndex = 0;
} }
this.NoInfluenceSupport.IsOn = Config.Global.IgnoreInfluenceSupport;
this.NoMarketBuy.IsOn = Config.Global.IgnoreMarketBuy;
this.NoFleetCarrier.IsOn = Config.Global.IgnoreFleetCarrier;
// Apply theme
try {
AddCustomThemes();
string[] colours = ThemeManager.Current.Themes
.Select(x => x.ColorScheme)
.DistinctBy(x => x)
.OrderBy(x => x)
.ToArray()
;
foreach (var colour in colours) {
Colour.Items.Add(colour);
if (!string.IsNullOrEmpty(Config.Global.Colour) &&
string.Compare(colour, Config.Global.Colour, true) == 0) {
Colour.SelectedIndex = Colour.Items.Count - 1;
}
}
if (Colour.SelectedIndex < 0) {
Colour.SelectedIndex = 0;
}
SwitchTheme.IsOn = (string.Compare(Config.Global.Theme, "dark", true) == 0);
ThemeManager.Current.ChangeTheme(this, Config.Global.FullTheme);
} catch (Exception) {
// Theme is invalid, revert back to our standard dark theme
Config.Global.Colour = "Amber";
Config.Global.Theme = "Dark";
}
journal = new PlayerJournal(Config.Global.JournalLocation); journal = new PlayerJournal(Config.Global.JournalLocation);
// Set both to now // Set both to now
startdate.SelectedDate = DateTime.Now; InitialiseTime();
enddate.SelectedDate = DateTime.Now;
journallocation.Text = Config.Global.JournalLocation; journallocation.Text = Config.Global.JournalLocation;
} }
private void AddCustomThemes() {
Dictionary<string, Color> colorThemes = new() {
//{ "HouseSalus", Color.FromRgb(0xBC, 0x94, 0x39) },
{ "HouseSalus", Color.FromRgb(0xED, 0xDA, 0x70) },
{ "NovaNavy", Color.FromRgb(0xA1, 0xA4, 0xDB) },
};
foreach (var colourtheme in colorThemes) {
var brush = new SolidColorBrush(colourtheme.Value);
// Add light theme
ThemeManager.Current.AddTheme(new Theme(
"Light." + colourtheme.Key,
"Light." + colourtheme.Key,
"Light",
colourtheme.Key,
colourtheme.Value,
brush,
true,
false)
);
// Add dark theme
ThemeManager.Current.AddTheme(new Theme(
"Dark." + colourtheme.Key,
"Dark." + colourtheme.Key,
"Dark",
colourtheme.Key,
colourtheme.Value,
brush,
true,
false)
);
}
}
private void InitialiseTime() {
DateTime today = DateTime.Today;
DateTime tomorrow = today.AddDays(1);
// HOCHKULTUR
startdate.Culture = enddate.Culture = CultureInfo.GetCultureInfo("de-AT");
startdate.SelectedDateTime = today;
enddate.SelectedDateTime = tomorrow;
}
private void TreeView_CheckBox_Updated(object sender, RoutedEventArgs args) { private void TreeView_CheckBox_Updated(object sender, RoutedEventArgs args) {
GenerateLog(); GenerateLog();
} }
private void Loadentries_EntriesLoaded(List<Entry> lines) {
try {
TransactionParser parser = new TransactionParser();
List<Transaction> transactions = parser.Parse(lines);
report = new Report(transactions);
this.entries.ItemsSource = report.Objectives;
} catch (Exception exception) {
Log("Something went terribly wrong while parsing the E:D player journal.");
Log("Please send this to CMDR Hekateh:");
Log(exception.ToString());
}
}
private void Report_OnLog(string message) { private void Report_OnLog(string message) {
StringBuilder builder = new StringBuilder(); StringBuilder builder = new StringBuilder();
@@ -92,12 +167,53 @@ public partial class MainWindow : Window {
Report_OnLog(message); Report_OnLog(message);
} }
private void HandleEntries(List<Entry> entries, DateTime start, DateTime end) {
try {
TransactionParser parser = new();
TransactionParserOptions options = new();
options.IgnoreInfluenceSupport = Config.Global.IgnoreInfluenceSupport;
options.IgnoreMarketBuy = Config.Global.IgnoreMarketBuy;
options.IgnoreFleetCarrierFaction = Config.Global.IgnoreFleetCarrier;
List<Transaction> transactions = parser.Parse(entries, options);
// Filter the transactions down to the given time frame
transactions = transactions
.Where(t => t.CompletedAtDateTime >= start && t.CompletedAtDateTime <= end)
.ToList()
;
List<IncompleteTransaction> incompletes = transactions.OfType<IncompleteTransaction>().ToList();
// Log incomplete and remove them from the results.
foreach (var incomplete in incompletes) {
Log(incomplete.Reason);
}
transactions.RemoveAll(x => incompletes.Contains(x));
report = new Report(transactions);
this.entries.ItemsSource = report.Objectives;
} catch (Exception exception) {
Log("Something went terribly wrong while parsing the E:D player journal.");
Log("Please send this to CMDR Hekateh:");
Log(exception.ToString());
}
}
private void HandleEntries(List<Entry> entries) {
HandleEntries(entries, startdate.SelectedDateTime ?? DateTime.Now, enddate.SelectedDateTime ?? DateTime.Now);
}
private void Loadentries_EntriesLoaded(List<Entry> lines) {
HandleEntries(lines);
}
private void ParseJournal_Click(object sender, RoutedEventArgs e) { private void ParseJournal_Click(object sender, RoutedEventArgs e) {
try { try {
TransactionParser parser = new TransactionParser(); TransactionParser parser = new TransactionParser();
DateTime start = startdate.SelectedDate ?? DateTime.Now; DateTime start = startdate.SelectedDateTime ?? DateTime.Now;
DateTime end = enddate.SelectedDate ?? DateTime.Now; DateTime end = enddate.SelectedDateTime ?? DateTime.Now;
journal.Open(); // Load all files journal.Open(); // Load all files
// Log files only get rotated if you restart the game client. This means that there might // Log files only get rotated if you restart the game client. This means that there might
@@ -108,25 +224,14 @@ public partial class MainWindow : Window {
// files have to be read in their entirety to check this). So we assume that you can't play // files have to be read in their entirety to check this). So we assume that you can't play
// three days straight, and keep the code fast. // three days straight, and keep the code fast.
DateTime actualstart = start.AddDays(-3); DateTime actualstart = start.AddDays(-3);
DateTime actualend = end.AddDays(1);
List<Entry> entries = journal.Files List<Entry> entries = journal.Files
.Where(f => f.NormalisedDateTime >= actualstart && f.NormalisedDateTime <= end) .Where(f => f.NormalisedDateTime >= actualstart && f.NormalisedDateTime <= actualend)
.SelectMany(e => e.Entries) .SelectMany(e => e.Entries)
.ToList() .ToList()
; ;
// Now further sort the list down to entries that are actually within the given datetime
// Note that entry datetimes are not normalised, so we have to sort until end + 1 day
DateTime actualend = end.AddDays(1);
entries = entries
.Where(e => e.Timestamp >= start && e.Timestamp < actualend)
.ToList()
;
List<Transaction> transactions = parser.Parse(entries);
report = new Report(transactions);
this.entries.ItemsSource = report.Objectives;
HandleEntries(entries, start, end);
GenerateLog(); GenerateLog();
} catch (Exception exception) { } catch (Exception exception) {
Log("Something went terribly wrong while parsing the E:D player journal."); Log("Something went terribly wrong while parsing the E:D player journal.");
@@ -172,7 +277,7 @@ public partial class MainWindow : Window {
} }
if (removed) { if (removed) {
GenerateLog(); RefreshView();
} }
} }
@@ -194,82 +299,39 @@ public partial class MainWindow : Window {
journal = new PlayerJournal(Config.Global.JournalLocation); journal = new PlayerJournal(Config.Global.JournalLocation);
} }
/// <summary> private Objective GetObjectiveFromControl(object sender) {
/// Gets the currently selected objective, even if a log entry in said objective Control control = sender as Control;
/// is selected instead. If nothing is selected, returns null. if (control == null || control.DataContext == null) {
/// </summary>
/// <returns></returns>
private Objective GetSelectedObjective() {
var obj = entries.SelectedItem;
if (obj == null) {
return null; return null;
} }
if (obj.GetType() == typeof(Objective)) { return control.DataContext as Objective;
return obj as Objective;
}
// Some form of entry perhaps?
if (obj.GetType().IsSubclassOf(typeof(Transaction))) {
Transaction entry = obj as Transaction;
Objective objective = entries.Items
.OfType<Objective>()
.First(x => x.Transactions.Contains(entry))
;
return objective;
}
return null;
} }
private void AddCombatZone_Click(object sender, RoutedEventArgs e) { private void AddCombatZone_Click(object sender, RoutedEventArgs e) {
Objective objective = GetSelectedObjective(); Objective objective = GetObjectiveFromControl(sender);
if (objective == null) { if (objective == null) {
return; return;
} }
CombatZoneDialog dialog = new CombatZoneDialog() { Owner = this };
if (!(dialog.ShowDialog() ?? false)) {
return;
}
CombatZone zone = new CombatZone { CombatZone zone = new CombatZone {
Faction = objective.Faction, Faction = objective.Faction,
System = objective.System, System = objective.System,
Grade = "Low",
Grade = dialog.Grade, Type = "Ship",
Type = dialog.Type,
Amount = dialog.Amount
}; };
objective.Transactions.Add(zone); UITransaction uitransaction = new UITransaction(zone);
objective.UITransactions.Add(uitransaction);
RefreshView();
}
private void RefreshView() {
entries.Items.Refresh();
GenerateLog(); GenerateLog();
} }
private void AdjustProfit_Click(object sender, RoutedEventArgs e) {
if (entries.SelectedItem == null || entries.SelectedItem.GetType() != typeof(SellCargo)) {
return;
}
SellCargo sell = entries.SelectedItem as SellCargo;
AdjustProfitWindow adjust = new AdjustProfitWindow() { Owner = this };
adjust.Profit.Text = sell.Profit.ToString();
if (!(adjust.ShowDialog() ?? false)) {
return;
}
if (int.TryParse(adjust.Profit.Text, out int newprofit)) {
sell.Profit = newprofit;
GenerateLog();
}
}
private void LogType_SelectionChanged(object sender, SelectionChangedEventArgs e) { private void LogType_SelectionChanged(object sender, SelectionChangedEventArgs e) {
if (LogType.SelectedItem == null) { if (LogType.SelectedItem == null) {
return; return;
@@ -299,5 +361,194 @@ public partial class MainWindow : Window {
private void window_Closing(object sender, System.ComponentModel.CancelEventArgs e) { private void window_Closing(object sender, System.ComponentModel.CancelEventArgs e) {
loadentries?.Close(); loadentries?.Close();
loadentries = null; loadentries = null;
try {
Config.SaveGlobal();
} catch (Exception error) {
MessageBox.Show("There was an error saving your settings: " + error.Message);
}
}
private void Transaction_Initialized(object sender, EventArgs e) {
}
private TransactionType GetTransaction<TransactionType>(object sender) where TransactionType : Transaction {
Control button = sender as Control;
if (button == null || button.DataContext == null) {
return null;
}
UITransaction transaction = button.DataContext as UITransaction;
if (transaction == null) {
return null;
}
return transaction.Transaction as TransactionType;
}
private void Low_Click(object sender, RoutedEventArgs e) {
CombatZone transaction = GetTransaction<CombatZone>(sender);
if (transaction == null) {
return;
}
transaction.Grade = CombatZones.DifficultyLow;
RefreshView();
}
private void Med_Click(object sender, RoutedEventArgs e) {
CombatZone transaction = GetTransaction<CombatZone>(sender);
if (transaction == null) {
return;
}
transaction.Grade = CombatZones.DifficultyMedium;
RefreshView();
}
private void High_Click(object sender, RoutedEventArgs e) {
CombatZone transaction = GetTransaction<CombatZone>(sender);
if (transaction == null) {
return;
}
transaction.Grade = CombatZones.DifficultyHigh;
RefreshView();
}
private void VeryHigh_Click(object sender, RoutedEventArgs e) {
CombatZone transaction = GetTransaction<CombatZone>(sender);
if (transaction == null) {
return;
}
transaction.Grade = CombatZones.DifficultyVeryHigh;
RefreshView();
}
private void Ground_Click(object sender, RoutedEventArgs e) {
CombatZone transaction = GetTransaction<CombatZone>(sender);
if (transaction == null) {
return;
}
transaction.Type = CombatZones.GroundCombatZone;
RefreshView();
}
private void Ship_Click(object sender, RoutedEventArgs e) {
CombatZone transaction = GetTransaction<CombatZone>(sender);
if (transaction == null) {
return;
}
transaction.Type = CombatZones.ShipCombatZone;
RefreshView();
}
private void Thargoid_Click(object sender, RoutedEventArgs e) {
CombatZone transaction = GetTransaction<CombatZone>(sender);
if (transaction == null) {
return;
}
transaction.Type = CombatZones.AXCombatZone;
RefreshView();
}
private void Profit_LostFocus(object sender, RoutedEventArgs e) {
RefreshView();
}
private void Profit_KeyUp(object sender, System.Windows.Input.KeyEventArgs e) {
if (e.Key == Key.Enter) {
RefreshView();
}
}
private DateTime ResetTimeToZero(DateTime d) {
DateTime obj = d;
obj = obj.AddHours(d.Hour * -1);
obj = obj.AddMinutes(d.Minute * -1);
obj = obj.AddSeconds(d.Second * -1);
return obj;
}
private void ResetTime_Click(object sender, RoutedEventArgs e) {
DateTime? d = startdate.SelectedDateTime;
if (d != null) {
startdate.SelectedDateTime = ResetTimeToZero(d.Value);
}
d = enddate.SelectedDateTime;
if (d != null) {
enddate.SelectedDateTime = ResetTimeToZero(d.Value);
}
}
private void ToggleAll_Click(object sender, RoutedEventArgs e) {
ToggleButton button = sender as ToggleButton;
Objective objective = GetObjectiveFromControl(sender);
if (objective == null) {
return;
}
objective.UITransactions
.ForEach(x => x.IsEnabled = (button.IsChecked ?? true))
;
}
private void UpdateTheme() {
ThemeManager.Current.ChangeTheme(this, Config.Global.FullTheme);
}
private void SwitchTheme_Toggled(object sender, RoutedEventArgs e) {
ToggleSwitch toggle = sender as ToggleSwitch;
if (toggle.IsOn) {
Config.Global.Theme = "Dark";
} else {
Config.Global.Theme = "Light";
}
UpdateTheme();
}
private void Colour_SelectionChanged(object sender, SelectionChangedEventArgs e) {
if (Colour.SelectedItem == null) {
return;
}
Config.Global.Colour = Colour.SelectedItem.ToString();
UpdateTheme();
}
private void URL_RequestNavigate(object sender, System.Windows.Navigation.RequestNavigateEventArgs e) {
ProcessStartInfo info = new ProcessStartInfo();
info.UseShellExecute = true;
info.FileName = e.Uri.AbsoluteUri;
Process.Start(info);
e.Handled = true;
}
private void NoInfluenceSupport_Toggled(object sender, RoutedEventArgs e) {
Config.Global.IgnoreInfluenceSupport = this.NoInfluenceSupport.IsOn;
}
private void NoMarketBuy_Toggled(object sender, RoutedEventArgs e) {
Config.Global.IgnoreMarketBuy = this.NoMarketBuy.IsOn;
}
private void NoFleetCarrier_Toggled(object sender, RoutedEventArgs e) {
Config.Global.IgnoreFleetCarrier = this.NoFleetCarrier.IsOn;
}
private void OpenInExplorer_Click(object sender, RoutedEventArgs e) {
try {
Process.Start(new ProcessStartInfo(Config.Global.JournalLocation) {
UseShellExecute = true,
});
} catch (Exception) {
}
} }
} }

View File

@@ -0,0 +1,19 @@
using System.Globalization;
using System.Windows.Data;
using System;
namespace EliteBGS;
public class MinusFortyFiveConverter : IValueConverter {
/// <inheritdoc/>
public object Convert(
object value, Type targetType, object parameter, CultureInfo culture) {
return (double)value - 80;
}
/// <inheritdoc/>
public object ConvertBack(
object value, Type targetType, object parameter, CultureInfo culture) {
throw new NotSupportedException("Cannot convert back");
}
}

View File

@@ -2,6 +2,7 @@
using System.Text; using System.Text;
using System.Globalization; using System.Globalization;
using EDPlayerJournal; using EDPlayerJournal;
using System.Linq;
namespace EliteBGS.BGS; namespace EliteBGS.BGS;
@@ -9,14 +10,15 @@ public class NonaDiscordLog : DiscordLogGenerator {
private string FormatDate() { private string FormatDate() {
CultureInfo cultureInfo = CultureInfo.InvariantCulture; CultureInfo cultureInfo = CultureInfo.InvariantCulture;
StringBuilder date = new StringBuilder(); StringBuilder date = new StringBuilder();
DateTime today = DateTime.Now; DateTime today = DateTime.UtcNow;
string suffix; string suffix;
if (today.Day == 1 || today.Day == 21 || today.Day == 31) { if (today.Day == 1 || today.Day == 21 || today.Day == 31) {
suffix = "st"; suffix = "st";
} else if (today.Day == 2 || today.Day == 22) { } else if (today.Day == 2 || today.Day == 22) {
suffix = "nd"; suffix = "nd";
} else if (today.Day == 23) { // Shakaka wins the price for finding this "bug"!
} else if (today.Day == 3 || today.Day == 23) {
suffix = "rd"; suffix = "rd";
} else { } else {
suffix = "th"; suffix = "th";
@@ -34,23 +36,46 @@ public class NonaDiscordLog : DiscordLogGenerator {
protected override string GenerateObjectiveHeader(Objective objective) { protected override string GenerateObjectiveHeader(Objective objective) {
StringBuilder log = new StringBuilder(); StringBuilder log = new StringBuilder();
log.AppendFormat(":globe_with_meridians: `Location:` {0}, {1}\n", objective.System, objective.Faction); string location;
if (!string.IsNullOrEmpty(objective.System) && !string.IsNullOrEmpty(objective.Faction)) {
location = string.Format("{0}, {1}", objective.System, objective.Faction);
} else if (!string.IsNullOrEmpty(objective.System)) {
location = objective.System;
} else {
location = "Unknown Location";
}
int legacycount = objective.Transactions
.Where(x => x.IsLegacy)
.Count()
;
string summary = GenerateSummary(objective);
log.AppendFormat(":globe_with_meridians: `Target:` {0}\n", location);
if (!string.IsNullOrEmpty(summary)) {
log.AppendFormat(":scroll: `Summary:` {0}\n", summary);
}
if (legacycount > 0) {
log.Append(":rotating_light: `Warning`: Some actions were done in E:D Legacy\n");
}
log.Append(":clipboard: `Conducted:`\n"); log.Append(":clipboard: `Conducted:`\n");
log.Append("```"); log.Append("```\n");
return log.ToString(); return log.ToString();
} }
protected override string GenerateObjectiveFooter(Objective objective) { protected override string GenerateObjectiveFooter(Objective objective) {
return "```"; return "```\n";
} }
protected override string GenerateHeader() { protected override string GenerateHeader() {
return string.Format(":clock2: `Date:` {0}", FormatDate()); return string.Format(":clock2: `Date:` {0}\n", FormatDate());
} }
protected override string GenerateFooter() { protected override string GenerateFooter() {
return ""; return "\n";
} }
public override string ToString() { public override string ToString() {

View File

@@ -4,14 +4,222 @@ using System.Text;
using Newtonsoft.Json; using Newtonsoft.Json;
using EDPlayerJournal.BGS; using EDPlayerJournal.BGS;
using System.Linq; using System.Linq;
using System.Windows;
using EDPlayerJournal;
using System.ComponentModel;
namespace EliteBGS; namespace EliteBGS;
public class UITransaction { public class UITransaction : INotifyPropertyChanged {
public bool IsEnabled { get; set; } = true; private bool isenabled = true;
public event PropertyChangedEventHandler PropertyChanged;
public bool IsEnabled {
get { return isenabled; }
set {
isenabled = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("IsEnabled"));
}
}
public bool IsExpanded { get; set; } = true; public bool IsExpanded { get; set; } = true;
public Visibility IsCombatZone {
get {
return (Transaction != null && Transaction.GetType() == typeof(CombatZone)) ? Visibility.Visible : Visibility.Hidden;
}
}
public Visibility IsSellCargo {
get {
return (Transaction != null && Transaction.GetType() == typeof(SellCargo)) ? Visibility.Visible : Visibility.Hidden;
}
}
public Visibility IsGroundCombatZone {
get {
CombatZone combat = Transaction as CombatZone;
if (combat == null) {
return Visibility.Hidden;
}
if (string.Compare(combat.Type, CombatZones.GroundCombatZone) == 0) {
return Visibility.Visible;
}
return Visibility.Hidden;
}
}
public Visibility IsShipCombatZone {
get {
CombatZone combat = Transaction as CombatZone;
if (combat == null) {
return Visibility.Hidden;
}
if (string.Compare(combat.Type, CombatZones.ShipCombatZone) == 0) {
return Visibility.Visible;
}
return Visibility.Hidden;
}
}
public Visibility IsAXCombatZone {
get {
CombatZone combat = Transaction as CombatZone;
if (combat == null) {
return Visibility.Hidden;
}
if (string.Compare(combat.Type, CombatZones.AXCombatZone) == 0) {
return Visibility.Visible;
}
return Visibility.Hidden;
}
}
public bool HasSpecOps {
get {
CombatZone combat = Transaction as CombatZone;
if (combat == null) {
return false;
}
return combat.SpecOps ?? false;
}
set {
CombatZone combat = Transaction as CombatZone;
if (combat == null) {
return;
}
combat.SpecOps = value;
}
}
public bool HasCapitalShip {
get {
CombatZone combat = Transaction as CombatZone;
if (combat == null) {
return false;
}
return combat.CapitalShip ?? false;
}
set {
CombatZone combat = Transaction as CombatZone;
if (combat == null) {
return;
}
combat.CapitalShip = value;
}
}
public bool HasEnemyCaptain {
get {
CombatZone combat = Transaction as CombatZone;
if (combat == null) {
return false;
}
return combat.EnemyCaptain ?? false;
}
set {
CombatZone combat = Transaction as CombatZone;
if (combat == null) {
return;
}
combat.EnemyCaptain = value;
}
}
public bool HasAlliedCaptain {
get {
CombatZone combat = Transaction as CombatZone;
if (combat == null) {
return false;
}
return combat.AlliedCaptain ?? false;
}
set {
CombatZone combat = Transaction as CombatZone;
if (combat == null) {
return;
}
combat.AlliedCaptain = value;
}
}
public bool HasEnemyCorrespondent {
get {
CombatZone combat = Transaction as CombatZone;
if (combat == null) {
return false;
}
return combat.EnemyCorrespondent ?? false;
}
set {
CombatZone combat = Transaction as CombatZone;
if (combat == null) {
return;
}
combat.EnemyCorrespondent = value;
}
}
public bool HasAlliedCorrespondent {
get {
CombatZone combat = Transaction as CombatZone;
if (combat == null) {
return false;
}
return combat.AlliedCorrespondent ?? false;
}
set {
CombatZone combat = Transaction as CombatZone;
if (combat == null) {
return;
}
combat.AlliedCorrespondent = value;
}
}
/// <summary>
/// Profit from selling, used in the XAML ui for binding
/// </summary>
public string Profit {
get {
SellCargo cargo = Transaction as SellCargo;
if (cargo == null) {
return "";
}
return cargo.Profit.ToString();
}
set {
SellCargo cargo = Transaction as SellCargo;
if (cargo == null) {
return;
}
try {
long profit_as_number = Convert.ToInt64(value);
cargo.Profit = profit_as_number;
} catch (FormatException) {
}
}
}
public Transaction Transaction { get; set; } public Transaction Transaction { get; set; }
public UITransaction() { } public UITransaction() { }
@@ -42,6 +250,14 @@ public class Objective : IComparable<Objective> {
get { return UITransactions.Select(x => x.Transaction).ToList<Transaction>(); } get { return UITransactions.Select(x => x.Transaction).ToList<Transaction>(); }
} }
public Visibility HasSystem {
get { return string.IsNullOrEmpty(System) ? Visibility.Hidden : Visibility.Visible; }
}
public Visibility HasFaction {
get { return string.IsNullOrEmpty(Faction) ? Visibility.Hidden : Visibility.Visible; }
}
public string Name { public string Name {
get { return this.ToString(); } get { return this.ToString(); }
} }
@@ -60,6 +276,10 @@ public class Objective : IComparable<Objective> {
string.Compare(faction, Faction) == 0; string.Compare(faction, Faction) == 0;
} }
public bool Matches(string system) {
return string.Compare(system, System) == 0;
}
public int CompareTo(Objective other) { public int CompareTo(Objective other) {
return (other.System == System && return (other.System == System &&
other.Faction == Faction) ? 0 : -1; other.Faction == Faction) ? 0 : -1;
@@ -77,6 +297,8 @@ public class Objective : IComparable<Objective> {
StringBuilder str = new StringBuilder(); StringBuilder str = new StringBuilder();
if (!string.IsNullOrEmpty(System)) { if (!string.IsNullOrEmpty(System)) {
str.AppendFormat("System: {0}", System); str.AppendFormat("System: {0}", System);
} else {
str.AppendFormat("System: Unknown");
} }
if (!string.IsNullOrEmpty(Faction)) { if (!string.IsNullOrEmpty(Faction)) {
if (str.Length > 0) { if (str.Length > 0) {

View File

@@ -0,0 +1,65 @@
using EliteBGS.LogGenerator;
using System.Linq;
using System.Text;
namespace EliteBGS;
public class OneLineDiscordLog : DiscordLogGenerator {
protected override string GenerateObjectiveHeader(Objective objective) {
if (!string.IsNullOrEmpty(objective.Faction)) {
return string.Format("**{0}** for **{1}**: ", objective.System, objective.Faction);
} else {
return string.Format("**{0}**: ", objective.System);
}
}
protected override string GenerateObjectiveFooter(Objective objective) {
return "\n";
}
protected override string GenerateFooter() {
return "";
}
protected override string GenerateHeader() {
return "";
}
public override string GenerateDiscordLog(Report report) {
StringBuilder log = new StringBuilder();
if (report == null) {
return "";
}
var objectives = report.Objectives
.Where(x => x.IsEnabled && x.Transactions.Count() > 0)
;
if (objectives == null || objectives.Count() <= 0) {
return "";
}
foreach (Objective objective in objectives) {
log.AppendFormat("{0}", GenerateObjectiveHeader(objective));
foreach (LogFormatter formatter in formatters) {
string text = formatter.GenerateSummary(objective);
text = text.Trim();
if (!string.IsNullOrEmpty(text)) {
log.AppendFormat("{0}; ", text);
}
}
log.AppendFormat("{0}", GenerateObjectiveFooter(objective));
}
log.AppendFormat("{0}", GenerateFooter());
return log.ToString().Trim();
}
public override string ToString() {
return "One Line Report";
}
}

View File

@@ -51,5 +51,5 @@ using System.Windows;
// You can specify all the values or you can default the Build and Revision Numbers // You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below: // by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")] // [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("0.1.1.0")] [assembly: AssemblyVersion("0.3.5.0")]
[assembly: AssemblyFileVersion("0.1.1.0")] [assembly: AssemblyFileVersion("0.3.5.0")]

View File

@@ -117,8 +117,4 @@
<resheader name="writer"> <resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader> </resheader>
<assembly alias="System.Windows.Forms" name="System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
<data name="EliteBGS" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\EliteBGS.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
</root> </root>

View File

@@ -1,11 +1,11 @@
# EliteBGS # EDBGS
This tool is meant to help people contributing to the BGS effort to create BGS reports. This tool is meant to help people contributing to the BGS effort to create BGS reports.
The tool allows you to configure BGS objectives, and will then parse your player journal The tool allows you to configure BGS objectives, and will then parse your player journal
for tasks you completed relating to that BGS objective. Once the JSON player journal has for tasks you completed relating to that BGS objective. Once the JSON player journal has
been parsed, you may then generate a BGS report you can copy/paste into Discord. been parsed, you may then generate a BGS report you can copy/paste into Discord.
Source code is available [here](https://git.aror.org/florian/elitebgs). Source code is available at [https://codeberg.org/nola/edbgs](https://codeberg.org/nola/edbgs).
Binary downloads can be found here: [https://bgs.n0la.org/](https://bgs.n0la.org/). Binary downloads can be found here: [https://bgs.n0la.org/](https://bgs.n0la.org/).
@@ -22,8 +22,13 @@ transactions. Currently the tool recognises the following transactions:
* Selling cartography data * Selling cartography data
* Selling of cargo to stations * Selling of cargo to stations
* Selling of micro resources (Odyssey only) * Selling of micro resources (Odyssey only)
* Selling of organic data (Odyssey only)
* Vouchers, including bounty vouchers, combat bonds, and settlement vouchers (aka intel packages) * Vouchers, including bounty vouchers, combat bonds, and settlement vouchers (aka intel packages)
* Thargoid kills
* Contributions to Thargoid war effort
The following transactions are recognised but not listed, because they do not affect BGS:
* Selling of organic data (Odyssey only)
Vouchers help the faction that is listed for them. If said faction is not present in the Vouchers help the faction that is listed for them. If said faction is not present in the
current system, then there is no BGS impact. So the tool looks for all system factions, and current system, then there is no BGS impact. So the tool looks for all system factions, and
@@ -221,13 +226,11 @@ It would be helpful if you included the JSON player journal. This player journal
## Build Dependencies ## Build Dependencies
Handling of Elite Dangerous player journals have been moved to a separate project called `EDJournal`. The project also requires:
Its source can be found [here](https://git.aror.org/florian/edjournal). This project simply depends
on the binary DLL that `EDJournal` builds.
The project also requires `Ookii.Dialogs.WPF` controls, which contains the auto complete text box. * `Ookii.Dialogs.WPF`
* `Newtonsoft.Json`
And of course, `Newtonsoft.Json` as the JSON parser. * `MahApps`
## About ## About

View File

@@ -18,10 +18,19 @@ public class Report {
} }
foreach (Transaction t in transactions) { foreach (Transaction t in transactions) {
Objective o = Objectives.Find(x => x.Matches(t.System, t.Faction)); Objective o;
if (t.SystemContribution) {
o = Objectives.Find(x => x.Matches(t.System));
} else {
o = Objectives.Find(x => x.Matches(t.System, t.Faction));
}
if (o == null) { if (o == null) {
if (t.SystemContribution) {
o = new Objective() { System = t.System };
} else {
o = new Objective() { Faction = t.Faction, System = t.System }; o = new Objective() { Faction = t.Faction, System = t.System };
}
Objectives.Add(o); Objectives.Add(o);
} }

Some files were not shown because too many files have changed in this diff Show More