diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..7e9a359 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,8 @@ +# These are supported funding model platforms + +github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] +patreon: linuxgurugamer +open_collective: # Replace with a single Open Collective username +ko_fi: # Replace with a single Ko-fi username +tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel +custom: # Replace with a single custom sponsorship URL diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f94dd14 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +obj/ +bin/ +*.user +*.suo +.vs/ +*~ +*.dll diff --git a/ChangeLog.txt b/ChangeLog.txt new file mode 100644 index 0000000..b78dfa4 --- /dev/null +++ b/ChangeLog.txt @@ -0,0 +1,186 @@ +ChangeLog + +1.9.20.5 + + Thanks to github user @averageksp for this: + Add German Translation for ScienceAlert + +1.9.20.4 + Thanks to github user @ch3zych3z for this: + Added RU localization + +1.9.20.3 + Thanks to github user @frankieorabona for this: + Updated Italian translation + +1.9.10.2 + Fixed path for loading files (broken by new launcher) + +1.9.10.1 + Fixed small memory leak caused by not releasing callback from GameEvents + +1.9.10 + Thanks to github user @peterhaneve for this: + Optimize profile saving (reduce persistent file size), saves space and speeds up loading and saving + +1.9.9.3 + Added AssemblyFileVersion + Updated version file for 1.12 + +1.9.9.2 + Changed fix to properly add a default key in the ProfileManager + +1.9.9.1 + Thanks to github user @kfsone for pointing out this error: + added missing ! in ScienceAlertProfileManager.cs to void a null ref exception + +1.9.9 + Disabled some unused code + Thanks to github user @frankieorabona for this: + Italian Localization + +1.9.8.9 + Added check to "ScienceAlertProfileManager.OnSave" check that a game is loaded before trying to save data + +1.9.8.8 + Replaced sheet-38 with original sheet-38 (animation was sharper) + Changed the loading of the sheets from the database to toolbarController.LoadImageFromFile + +1.9.8.7 + Fixed Collect all to be visible even when only one science container on vessel + Thanks to @Fitiales for a spanish translation + a single Kerbal doesn't have a "collect all" action. + Added support for the DMagic experiments + Added support for DMModuleScienceAnimateGeneric + Added check for old version of DMModuleScienceAnimateGeneric (disables if less than 0.23) + Thanks to githb user @Fitiales: + Added spanish translation + +1.9.8.6 + Fixed initialization of variables in DraggableWindow (two Awake() methods were conflicting) + +1.9.8.5 + Moved initialization of variables into Awake + +1.9.8.4 + Moved the Localization directory & file into the release directory + +1.9.8.3 + Some localization changes + +1.9.8.2 + Replaced embedded resource images with files + Removed 120 lines related to embedded resource images + Found a static GUISkin which was causing the options window to be double width + Commented out a line in Settings which was setting the background to null + Restored the missing controls to close the window, lock the window, etc + +1.9.8.1 + Thanks to @Gordon_Dry for this: + Simplification of zzz_fix_biomeMask_situationMask_0.cfg + Sanity fix for RP-0 (RP-1) + Update zzz_fix_biomeMask_situationMask_0.cfg + + See + https://forum.kerbalspaceprogram.com/index.php?/topic/175772-15-* + and following posts. A simplification of the patch. + +1.9.8 + Updated for KSP 1.8 + +1.9.7.1 + Fixed "Deploy All" allowing an eva when eva's aren't allowed + +1.9.7 + Added Install Checker + Updated AssemblyVersion + +1.9.6.2 + Fixed Collect All button to be visible whenever any science container has spare capacity + (note that most science containers seem to have unlimited capacity) + +1.9.6.1 + Added button to mapview + +1.9.6 + Fixed the Collect All so it now works with science containers which have a gui available in the PAW + Added check so that if no science containers are available, the button will be greyed out + Now doesn't show the Collect All if no data is available to be collected + +1.9.5.4 + Replaced ugly GOTO code + Added more checks for null in StorageCache.cs in RemoveMagicTransmitter()"" + vessel.rootPart.Modules == null || vessel.Parts == null) + +1.9.5.2 + Fixed saving of profiles + Fixed loading of profiles + Some optimizations by reducing duplicate calls + Following due to changes in KSP + Changed the OnLoad in the ScienceAlertProfileManager to calling a CoRoutine so it can wait until the scene is ready + Changed the OnVesselChange in the ScienceAlertProfileManager to calling a CoRoutine so it can wait until the OnLoad is complete + +1.9.5.1 + Fixed issue where tourists could EVA + Updated .version file + +1.9.5 + Fixed bug where coming out of timewarp to get science would lock the maneuver node + +1.9.4.4 + Fixed download link in .version file + Moved ModuleManager configs into new folder called MM_Configs + Added contributed file from forum user @Gordon Dry to fix issues where biomeMask and situationMask are 0 + +1.9.4.3 + Version bump for 1.5 rebuild + +1.9.4.2 + Thanks to github user @dariasc for this: + Change SurfaceSampleObserver requirements: Changed to go inline with the current status. + https://wiki.kerbalspaceprogram.com/wiki/Research_and_Development#Levels + +1.9.4.1 + Following from 4x4cheesecake: + Science Alert now works along Kerbalism + Stored experiments are detected in all loaded vessels + Removed unnecessary line from AssemblyVersion.tt: <#@ assembly name="EnvDTE" #> + +1.9.4 + Thanks to github & forum user @jefftimlin for this fix: + The BiomeFilter class attempted to generate a clean biome texture for the purposes of more accurate biome detection, + however it's expensive and complicated. Completely removed all of the complex code from this class to avoid the threading + issue entirely, and just made it use the ScienceUtil.GetExperimentBiome function, which seems to work well. + It makes the code faster, simpler, and safer. + Fixed an array index out of bounds problem in RequiresCrew.cs, which I found while debugging, which would intermittently + produce errors in the logs. + Added Collect All button to collect all science into any/all containers + + +1.9.3 + Added two colors to the buttons: + Yellow if the experiment needs to be reset before rerunning it + Red if the experiment cannot be reset + +1.9.2.3 + Official release + + Fixed nullref when going from flight to spacecenter + New icons for stock toolbar + Removed log spam for animation + Replaced foreach with for int on lists + Added a "Deploy All" button + + +1.9.2.2 + Deleted about 1150 lines of unneeded code related to the Toolbar + Added exclusion list for experiments and manufacturers + +1.9.2.1 + fixed the missed repopulating a list after a revert?? + +1.9.2 + Adoption by LGG + Added support for ToolbarController + Added support for the ClickThrough Blocker + Removed old code used for the Blizzy Toolbar diff --git a/GameData/ScienceAlert/LICENSE.txt b/GameData/ScienceAlert/LICENSE.txt new file mode 100644 index 0000000..94a0453 --- /dev/null +++ b/GameData/ScienceAlert/LICENSE.txt @@ -0,0 +1,621 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS diff --git a/GameData/ScienceAlert/Localization/de-de.cfg b/GameData/ScienceAlert/Localization/de-de.cfg new file mode 100644 index 0000000..c3b699f --- /dev/null +++ b/GameData/ScienceAlert/Localization/de-de.cfg @@ -0,0 +1,78 @@ +Localization +{ + de-de + { + #ScienceAlert_title = Verfügbare Experiemente + #ScienceAlert_HaveScanData = Daten nicht gefunden + #ScienceAlert_Available = Keine Experiemente verfügbar + #ScienceAlert_DeployAll = Alle ausfahren + #ScienceAlert_DeployexEVA = Alle ausfahren, außer EVA + #ScienceAlert_CollectAll = Alles einsammeln + #ScienceAlert_Nocontainers = Alles einsammeln (keine Wissenschafts Behälter verfügbar) + + // OptionUI + #ScienceAlert_Optitle = ScienceAlarm Optionen + + #ScienceAlert_label1 = Globale Zeitraffer Einstellungen + #ScienceAlert_label2 = Globaler Alarm Ton + #ScienceAlert_label3 = Weitere Optionen + #ScienceAlert_label4 = UI-Einstellungen + #ScienceAlert_label5 = Globale Animationen anmachen + #ScienceAlert_label6 = Fenster Kapazität + #ScienceAlert_label7 = Weniger + #ScienceAlert_label8 = Mehr + #ScienceAlert_label9 = Optionen für die Integration von Drittanbietern + #ScienceAlert_label10 = Einstellungen für bemannte Raumfahrzeuge + #ScienceAlert_label11 = Profile: <<1>> + #ScienceAlert_label12 = Alarmgrenze + #ScienceAlert_label13 = Alarmgrenze: <<1>> + #ScienceAlert_label14 = (deaktiviert) + #ScienceAlert_label15 = Wissenschaftsmenge + #ScienceAlert_label16 = Kein Profil aktiv + #ScienceAlert_label17 = Wählen Sie ein Profil zum Laden + #ScienceAlert_label18 = Keine Profile gespeichert + #ScienceAlert_label19 = '<<1>>' Speichern? + #ScienceAlert_label20 = Profil '<<1>>' existiert bereits! + + #ScienceAlert_button1 = Ununtersucht + #ScienceAlert_button2 = Nicht maximal + #ScienceAlert_button3 = < 50% gesammelt + #ScienceAlert_button4 = < 90% gesammelt + #ScienceAlert_button5 = Nach Experiment + #ScienceAlert_button6 = Global ein + #ScienceAlert_button7 = Global aus + #ScienceAlert_button8 = Immer + #ScienceAlert_button9 = Nie + #ScienceAlert_button10 = Abbruch + #ScienceAlert_button11 = SPEICHERN + #ScienceAlert_button12 = ABBRUCH + #ScienceAlert_button13 = Überschreiben + #ScienceAlert_button14 = UMBENNEN + #ScienceAlert_button15 = Bestätigen + + #ScienceAlert_toggle1 = Berichtswert anzeigen + #ScienceAlert_toggle2 = Biome in der Experimentliste anzeigen + #ScienceAlert_toggle3 = EVA-Bericht zuerst auflisten + #ScienceAlert_toggle4 = SCANsat-Integration aktivieren + #ScienceAlert_toggle5 = Oberflächenprobe im Raumschiff verfolgen + #ScienceAlert_toggle6 = Aktiviert + #ScienceAlert_toggle7 = Animation bei Entdeckung + #ScienceAlert_toggle8 = Ton bei Entdeckung + #ScienceAlert_toggle9 = Zeitraffer bei Entdeckung stoppen + #ScienceAlert_toggle10 = Filtermethode + + #ScienceAlert_Msg1title = SCANsat nicht gefunden + #ScienceAlert_Msg1 = SCANsat wurde nicht gefunden. Du musst SCANsat installieren, um diese Funktion zu nutzen. + #ScienceAlert_Msg1_button1 = Okay + + #ScienceAlert_Msg2title = '<<1>>' umbenennen in: + #ScienceAlert_Msg2 = '<<1>>' existiert bereits. Überschreiben? + #ScienceAlert_Msg2title2 = Zielprofil umbenennen + #ScienceAlert_Msg2_button1 = Ja + #ScienceAlert_Msg2_button2 = Nein + + #ScienceAlert_Msg3 = Bist du sicher, dass du '<<1>>' löschen möchtest? + #ScienceAlert_Msg3_2 = '<<1>>' laden?\nNicht gespeicherte Einstellungen gehen verloren. + #ScienceAlert_Msg3title = Wissenschafts-Alarm Popup öffnen + } +} diff --git a/GameData/ScienceAlert/Localization/en-us.cfg b/GameData/ScienceAlert/Localization/en-us.cfg new file mode 100644 index 0000000..5f86e8b --- /dev/null +++ b/GameData/ScienceAlert/Localization/en-us.cfg @@ -0,0 +1,78 @@ +Localization +{ + en-us + { + #ScienceAlert_title = Available Experiments + #ScienceAlert_HaveScanData = Data not found + #ScienceAlert_Available = no experiments available + #ScienceAlert_DeployAll = Deploy All + #ScienceAlert_DeployexEVA = Deploy All except EVA + #ScienceAlert_CollectAll = Collect All + #ScienceAlert_Nocontainers = Collect All (no science containers available) + + //OptionUI + #ScienceAlert_Optitle = ScienceAlert Options + + #ScienceAlert_label1 = Global Warp Settings + #ScienceAlert_label2 = Global Alert Sound + #ScienceAlert_label3 = Additional Options + #ScienceAlert_label4 = User Interface Settings + #ScienceAlert_label5 = Globally Enable Animation + #ScienceAlert_label6 = Window Opacity + #ScienceAlert_label7 = Less + #ScienceAlert_label8 = More + #ScienceAlert_label9 = Third-party Integration Options + #ScienceAlert_label10 = Crewed Vessel Settings + #ScienceAlert_label11 = Profile: <<1>> + #ScienceAlert_label12 = Alert Threshold + #ScienceAlert_label13 = Alert Threshold: <<1>> + #ScienceAlert_label14 = (disabled) + #ScienceAlert_label15 = Science Amount + #ScienceAlert_label16 = No profile active + #ScienceAlert_label17 = Select a profile to load + #ScienceAlert_label18 = No profiles saved + #ScienceAlert_label19 = SAVE '<<1>>'? + #ScienceAlert_label20 = Profile '<<1>>' already exists! + + #ScienceAlert_button1 = Unresearched + #ScienceAlert_button2 = Not maxed + #ScienceAlert_button3 = < 50% collected + #ScienceAlert_button4 = < 90% collected + #ScienceAlert_button5 = By Experiment + #ScienceAlert_button6 = Globally on + #ScienceAlert_button7 = Globally off + #ScienceAlert_button8 = Always + #ScienceAlert_button9 = Never + #ScienceAlert_button10 = Cancel + #ScienceAlert_button11 = SAVE + #ScienceAlert_button12 = CANCEL + #ScienceAlert_button13 = Overwrite + #ScienceAlert_button14 = RENAME + #ScienceAlert_button15 = Confirm + + #ScienceAlert_toggle1 = Display Report Value + #ScienceAlert_toggle2 = Display Biome in Experiment List + #ScienceAlert_toggle3 = List EVA Report first + #ScienceAlert_toggle4 = Enable SCANsat integration + #ScienceAlert_toggle5 = Track surface sample in vessel + #ScienceAlert_toggle6 = Enabled + #ScienceAlert_toggle7 = Animation on discovery + #ScienceAlert_toggle8 = Sound on discovery + #ScienceAlert_toggle9 = Stop warp on discovery + #ScienceAlert_toggle10 = Filter Method + + #ScienceAlert_Msg1title = SCANsat Not Found + #ScienceAlert_Msg1 = SCANsat was not found. You must install SCANsat to use this feature. + #ScienceAlert_Msg1_button1 = Okay + + #ScienceAlert_Msg2title = Rename '<<1>>' to: + #ScienceAlert_Msg2 = '<<1>>' already exists. Overwrite? + #ScienceAlert_Msg2title2 = Rename Target Profile + #ScienceAlert_Msg2_button1 = Yes + #ScienceAlert_Msg2_button2 = No + + #ScienceAlert_Msg3 = Are you sure you want to\ndelete '<<1>>'? + #ScienceAlert_Msg3_2 = Load '<<1>>'?\nUnsaved settings will be lost. + #ScienceAlert_Msg3title = Science Alert Open Popup + } +} diff --git a/GameData/ScienceAlert/Localization/es-es.cfg b/GameData/ScienceAlert/Localization/es-es.cfg new file mode 100644 index 0000000..fe77220 --- /dev/null +++ b/GameData/ScienceAlert/Localization/es-es.cfg @@ -0,0 +1,78 @@ +Localization +{ + es-es + { + #ScienceAlert_title = Experimentos Disponibles + #ScienceAlert_HaveScanData = Datos no encontrados + #ScienceAlert_Available = no hay experimentos disponibles + #ScienceAlert_DeployAll = Desplegar todo + #ScienceAlert_DeployexEVA = Desplegar todo excepto AEV + #ScienceAlert_CollectAll = Recoger Todo + #ScienceAlert_Nocontainers = Recoger Todo (no hay contenedores de ciencias disponibles) + + //OptionUI + #ScienceAlert_Optitle = Opciones de ScienceAlert + + #ScienceAlert_label1 = Configuración global de Deformación + #ScienceAlert_label2 = Sonido de alerta global + #ScienceAlert_label3 = Opciones adicionales + #ScienceAlert_label4 = Configuraciones de interfaz de usuario + #ScienceAlert_label5 = Habilitar globalmente animación + #ScienceAlert_label6 = Opacidad de ventana + #ScienceAlert_label7 = Menos + #ScienceAlert_label8 = Más + #ScienceAlert_label9 = Opciones de integración de terceros + #ScienceAlert_label10 = Configuración de la nave con tripulación + #ScienceAlert_label11 = Perfil: <<1>> + #ScienceAlert_label12 = Umbral de alerta + #ScienceAlert_label13 = Umbral de alerta: <<1>> + #ScienceAlert_label14 = (deshabilitado) + #ScienceAlert_label15 = Cantidad de ciencia + #ScienceAlert_label16 = Ningún perfil activo + #ScienceAlert_label17 = Seleccione un perfil para cargar + #ScienceAlert_label18 = No hay perfiles guardados + #ScienceAlert_label19 = SALVAR '<<1>>'? + #ScienceAlert_label20 = ¡El perfil '<<1>>' ya existe! + + #ScienceAlert_button1 = No investigado + #ScienceAlert_button2 = No maximizado + #ScienceAlert_button3 = < 50% recogido + #ScienceAlert_button4 = < 90% recogido + #ScienceAlert_button5 = Por Experimento + #ScienceAlert_button6 = Globalmente encendido + #ScienceAlert_button7 = Globalmente apagado + #ScienceAlert_button8 = Siempre + #ScienceAlert_button9 = Nunca + #ScienceAlert_button10 = Cancelar + #ScienceAlert_button11 = SALVAR + #ScienceAlert_button12 = CANCELAR + #ScienceAlert_button13 = Sobrescribir + #ScienceAlert_button14 = RENOMBRAR + #ScienceAlert_button15 = Confirmar + + #ScienceAlert_toggle1 = Mostrar valor del informe + #ScienceAlert_toggle2 = Mostrar bioma en la lista de experimentos + #ScienceAlert_toggle3 = Listar el Informe AEV primero + #ScienceAlert_toggle4 = Habilitar la integración SCANsat + #ScienceAlert_toggle5 = Seguir la muestra de superficie en la nave + #ScienceAlert_toggle6 = Habilitado + #ScienceAlert_toggle7 = Animación al descubrir + #ScienceAlert_toggle8 = Sonido al descubrir + #ScienceAlert_toggle9 = Detener la deformación al descubrir + #ScienceAlert_toggle10 = Método de filtro + + #ScienceAlert_Msg1title = SCANsat no encontrado + #ScienceAlert_Msg1 = SCANsat no se encontró. Debe instalar SCANsat para usar esta función. + #ScienceAlert_Msg1_button1 = OK + + #ScienceAlert_Msg2title = Renombrar '<<1>>' a: + #ScienceAlert_Msg2 = '<<1>>' ya existe. ¿Sobrescribir? + #ScienceAlert_Msg2title2 = Renombrar perfil de destino + #ScienceAlert_Msg2_button1 = Sí + #ScienceAlert_Msg2_button2 = No + + #ScienceAlert_Msg3 = ¿Estás seguro de que quieres\neliminar '<<1>>'? + #ScienceAlert_Msg3_2 = ¿Cargar '<<1>>'?\nLa configuración no guardada se perderá. + #ScienceAlert_Msg3title = Ventana emergente de alerta de ciencia + } +} diff --git a/GameData/ScienceAlert/Localization/it-it.cfg b/GameData/ScienceAlert/Localization/it-it.cfg new file mode 100644 index 0000000..158432b --- /dev/null +++ b/GameData/ScienceAlert/Localization/it-it.cfg @@ -0,0 +1,78 @@ +Localization +{ + it-it + { + #ScienceAlert_title = Esperimenti Disponibili + #ScienceAlert_HaveScanData = Dati non trovati + #ScienceAlert_Available = nessun esperimento disponibile + #ScienceAlert_DeployAll = Distribuisci tutto + #ScienceAlert_DeployexEVA = Distribuisci tutto tranne EVA + #ScienceAlert_CollectAll = Raccogli tutto + #ScienceAlert_Nocontainers = Raccogli tutto (nessun contenitore scientifico disponibile) + + //OptionUI + #ScienceAlert_Optitle = Opzioni ScienceAlert + + #ScienceAlert_label1 = Global Warp Settings + #ScienceAlert_label2 = Global Alert Sound + #ScienceAlert_label3 = Opzioni Aggiuntive + #ScienceAlert_label4 = Impostazioni Interfaccia UTente + #ScienceAlert_label5 = Abilita Animazione Globalmente + #ScienceAlert_label6 = Opacità FInestra + #ScienceAlert_label7 = Meno + #ScienceAlert_label8 = Più + #ScienceAlert_label9 = Opzioni di integrazione di terze parti + #ScienceAlert_label10 = Impostazioni Veicolo con Equipaggio + #ScienceAlert_label11 = Profilo: <<1>> + #ScienceAlert_label12 = Soglia di Allarme + #ScienceAlert_label13 = Soglia di Allarme: <<1>> + #ScienceAlert_label14 = (disabilitato) + #ScienceAlert_label15 = Quantità di Scienza + #ScienceAlert_label16 = Nessun profilo attivo + #ScienceAlert_label17 = Selezionare un profilo da caricare + #ScienceAlert_label18 = Nessun profilo salvato + #ScienceAlert_label19 = SALVA '<<1>>'? + #ScienceAlert_label20 = Il profilo '<<1>>' esiste già! + + #ScienceAlert_button1 = Non ricercato + #ScienceAlert_button2 = Not maxed + #ScienceAlert_button3 = < 50% raccolta + #ScienceAlert_button4 = < 90% raccolta + #ScienceAlert_button5 = Per Esperimento + #ScienceAlert_button6 = Globalmente on + #ScienceAlert_button7 = Globalmente off + #ScienceAlert_button8 = Sempre + #ScienceAlert_button9 = Mai + #ScienceAlert_button10 = Annulla + #ScienceAlert_button11 = SALVA + #ScienceAlert_button12 = ANNULLA + #ScienceAlert_button13 = Sovrascrivi + #ScienceAlert_button14 = RINOMINA + #ScienceAlert_button15 = Conferma + + #ScienceAlert_toggle1 = Mostra Valore Rapporto + #ScienceAlert_toggle2 = Visualizza Bioma nella Lista Esperimenti + #ScienceAlert_toggle3 = Elenca Prima Rapporto EVA + #ScienceAlert_toggle4 = Abilita l'integrazione di SCANsat + #ScienceAlert_toggle5 = Traccia campione di superficie sul veicolo + #ScienceAlert_toggle6 = Abilitato + #ScienceAlert_toggle7 = Animazione sulla scoperta + #ScienceAlert_toggle8 = Suono alla scoperta + #ScienceAlert_toggle9 = Ferma Accellerazione Temp. alla scoperta + #ScienceAlert_toggle10 = Metodo del filtro + + #ScienceAlert_Msg1title = SCANsat Non Trovato + #ScienceAlert_Msg1 = SCANsat non è stato trovato. È necessario installare SCANsat per utilizzare questa funzione. + #ScienceAlert_Msg1_button1 = Okay + + #ScienceAlert_Msg2title = Rinomina '<<1>>' in: + #ScienceAlert_Msg2 = '<<1>>' esiste già. Vuoi sovrascriverlo? + #ScienceAlert_Msg2title2 = Rinomina Profilo Obiettivo + #ScienceAlert_Msg2_button1 = Sì + #ScienceAlert_Msg2_button2 = No + + #ScienceAlert_Msg3 = Sei sicuro di voler\neliminare '<<1>>'? + #ScienceAlert_Msg3_2 = Carica '<<1>>'?\nLE impostazioni non salvate saranno perse. + #ScienceAlert_Msg3title = Finestra pop-up di avviso scientifico + } +} diff --git a/GameData/ScienceAlert/Localization/ru-ru.cfg b/GameData/ScienceAlert/Localization/ru-ru.cfg new file mode 100644 index 0000000..6ca5c1e --- /dev/null +++ b/GameData/ScienceAlert/Localization/ru-ru.cfg @@ -0,0 +1,78 @@ +Localization +{ + ru-ru + { + #ScienceAlert_title = Доступные эксперименты + #ScienceAlert_HaveScanData = Данные не найдены + #ScienceAlert_Available = нет доступных экспериментов + #ScienceAlert_DeployAll = Провести все + #ScienceAlert_DeployexEVA = Провести все, кроме внекорабельной деятельности + #ScienceAlert_CollectAll = Собрать все + #ScienceAlert_Nocontainers = Собрать все (нет доступных контейнеров для экспериментов) + + //OptionUI + #ScienceAlert_Optitle = Настройки ScienceAlert + + #ScienceAlert_label1 = Глобальные настройки ускорения времени + #ScienceAlert_label2 = Глобальные настройки звуков уведомлений + #ScienceAlert_label3 = Дополнительные настройки + #ScienceAlert_label4 = Настройки интерфейса + #ScienceAlert_label5 = Глобально включить анимацию + #ScienceAlert_label6 = Прозрачность окон + #ScienceAlert_label7 = Меньше + #ScienceAlert_label8 = Больше + #ScienceAlert_label9 = Настройки интеграции с другими модами + #ScienceAlert_label10 = Настройки корабля с экипажем + #ScienceAlert_label11 = Профиль: <<1>> + #ScienceAlert_label12 = Порог уведомления + #ScienceAlert_label13 = Порог уведомления: <<1>> + #ScienceAlert_label14 = (отключено) + #ScienceAlert_label15 = Количество очков науки + #ScienceAlert_label16 = Нет активного профиля + #ScienceAlert_label17 = Выберите профиль для загрузки + #ScienceAlert_label18 = Ни один профиль не сохранен + #ScienceAlert_label19 = Сохранить '<<1>>'? + #ScienceAlert_label20 = Профиль '<<1>>' уже существует! + + #ScienceAlert_button1 = Не исследовано + #ScienceAlert_button2 = Не получено максимального значения + #ScienceAlert_button3 = < 50% собрано + #ScienceAlert_button4 = < 90% собрано + #ScienceAlert_button5 = By Experiment + #ScienceAlert_button6 = Глобально включено + #ScienceAlert_button7 = Глобально выключено + #ScienceAlert_button8 = Всегда + #ScienceAlert_button9 = Никогда + #ScienceAlert_button10 = Отменить + #ScienceAlert_button11 = Сохранить + #ScienceAlert_button12 = Отменить + #ScienceAlert_button13 = Перезаписать + #ScienceAlert_button14 = Переименовать + #ScienceAlert_button15 = Подтвердить + + #ScienceAlert_toggle1 = Показывать отчет + #ScienceAlert_toggle2 = Показывать биом в списке экспериментов + #ScienceAlert_toggle3 = Отображать отчет о внекорабельной деятельности первым + #ScienceAlert_toggle4 = Включить интеграцию с SCANsat + #ScienceAlert_toggle5 = Отслеживать образец грунта внутри корабля + #ScienceAlert_toggle6 = Включено + #ScienceAlert_toggle7 = Анимация при возможности провести эксперимент + #ScienceAlert_toggle8 = Звук при возможности провести эксперимент + #ScienceAlert_toggle9 = Выключить ускорение времени при возможности провести эксперимент + #ScienceAlert_toggle10 = Метод фильтрации + + #ScienceAlert_Msg1title = SCANsat не обнаружен + #ScienceAlert_Msg1 = SCANsat не обнаружен. Чтобы использовать это, Вы должны установить SCANsat. + #ScienceAlert_Msg1_button1 = Понятно + + #ScienceAlert_Msg2title = Переименоваьт '<<1>>' в: + #ScienceAlert_Msg2 = '<<1>>' уже существует. Перезаписать? + #ScienceAlert_Msg2title2 = Переименовать профиль? + #ScienceAlert_Msg2_button1 = Да + #ScienceAlert_Msg2_button2 = Нет + + #ScienceAlert_Msg3 = Вы уверены, что хотите\nудалить '<<1>>'? + #ScienceAlert_Msg3_2 = Загрузить '<<1>>'?\nНесохраненные данные будут потеряны. + #ScienceAlert_Msg3title = Всплывающее окно Science Alert + } +} diff --git a/GameData/ScienceAlert/MM_Configs/ExcludedExperiments.cfg b/GameData/ScienceAlert/MM_Configs/ExcludedExperiments.cfg new file mode 100644 index 0000000..26a26bd --- /dev/null +++ b/GameData/ScienceAlert/MM_Configs/ExcludedExperiments.cfg @@ -0,0 +1,47 @@ +KEI_EXCLUDED_EXPERIMENTS +{ + experiment = SEP_SolarwindSpectrum + experiment = SEP_CCIDscan + experiment = error + experiment = waitWhat + + experiment = hullcampicture + + // Following from the WildBlueIndustries mod + experiment = WBISpaceResearch + experiment = WBISpaceAdaptionStudy + experiment = WBIPowerToolsEvaluation + experiment = WBIConstructionTechniques + experiment = WBICrystalGrowth + experiment = WBIIceCreamResearch + experiment = WBITemperatureStudy + experiment = WBIThermalStudy + experiment = WBIGooStudy + experiment = WBICryogenicResourceStudy + experiment = WBILongTermCryogenicMiniStudy + experiment = WBIMESS + experiment = WBICryogenicStudy + experiment = WBILongTermCryogenicStudy + experiment = WBICryogenicRadiationStudy + experiment = WBISurfaceConstructionStudy + experiment = WBIKNUTS + experiment = WBIBRE + experiment = WBISAME + experiment = WBICoreSampleAnalysis + experiment = WBIBiomeAnalysis + experiment = WBISoilAnalysis + experiment = WBIMetallurgyAnalysis + experiment = WBIChemicalAnalysis + experiment = WBIExtractionAnalysis + + experiment = GravioliumStudy + experiment = wbiSaucerResearchExperiment + + + // Nehemia Engineering Orbital Science + experiment = NE_KEES_ODC + experiment = NE_KEES_POSA1 + experiment = NE_KEES_POSA2 + experiment = NE_KEES_PPMD + experiment = NE_KEES_TEST +} diff --git a/GameData/ScienceAlert/MM_Configs/ExcludedManufacturers.cfg b/GameData/ScienceAlert/MM_Configs/ExcludedManufacturers.cfg new file mode 100644 index 0000000..736ab9e --- /dev/null +++ b/GameData/ScienceAlert/MM_Configs/ExcludedManufacturers.cfg @@ -0,0 +1,4 @@ +KEI_EXCLUDED_MANUFACTURERS +{ + manufacturer = Station Science Directorate +} diff --git a/Textures/btnBackground.png b/GameData/ScienceAlert/PluginData/Textures/btnBackground.png similarity index 100% rename from Textures/btnBackground.png rename to GameData/ScienceAlert/PluginData/Textures/btnBackground.png diff --git a/Textures/btnClose.png b/GameData/ScienceAlert/PluginData/Textures/btnClose.png similarity index 100% rename from Textures/btnClose.png rename to GameData/ScienceAlert/PluginData/Textures/btnClose.png diff --git a/Textures/btnDelete.png b/GameData/ScienceAlert/PluginData/Textures/btnDelete.png similarity index 100% rename from Textures/btnDelete.png rename to GameData/ScienceAlert/PluginData/Textures/btnDelete.png diff --git a/Textures/btnExpand.png b/GameData/ScienceAlert/PluginData/Textures/btnExpand.png similarity index 100% rename from Textures/btnExpand.png rename to GameData/ScienceAlert/PluginData/Textures/btnExpand.png diff --git a/Textures/btnLock.png b/GameData/ScienceAlert/PluginData/Textures/btnLock.png similarity index 100% rename from Textures/btnLock.png rename to GameData/ScienceAlert/PluginData/Textures/btnLock.png diff --git a/Textures/btnOpen.png b/GameData/ScienceAlert/PluginData/Textures/btnOpen.png similarity index 100% rename from Textures/btnOpen.png rename to GameData/ScienceAlert/PluginData/Textures/btnOpen.png diff --git a/Textures/btnRename.png b/GameData/ScienceAlert/PluginData/Textures/btnRename.png similarity index 100% rename from Textures/btnRename.png rename to GameData/ScienceAlert/PluginData/Textures/btnRename.png diff --git a/Textures/btnSave.png b/GameData/ScienceAlert/PluginData/Textures/btnSave.png similarity index 100% rename from Textures/btnSave.png rename to GameData/ScienceAlert/PluginData/Textures/btnSave.png diff --git a/Textures/btnUnlock.png b/GameData/ScienceAlert/PluginData/Textures/btnUnlock.png similarity index 100% rename from Textures/btnUnlock.png rename to GameData/ScienceAlert/PluginData/Textures/btnUnlock.png diff --git a/GameData/ScienceAlert/PluginData/Textures/flask-38.png b/GameData/ScienceAlert/PluginData/Textures/flask-38.png new file mode 100644 index 0000000..5bcad7a Binary files /dev/null and b/GameData/ScienceAlert/PluginData/Textures/flask-38.png differ diff --git a/GameData/ScienceAlert/PluginData/Textures/flask.png b/GameData/ScienceAlert/PluginData/Textures/flask.png new file mode 100644 index 0000000..2058c08 Binary files /dev/null and b/GameData/ScienceAlert/PluginData/Textures/flask.png differ diff --git a/GameData/ScienceAlert/PluginData/Textures/flask_256.png b/GameData/ScienceAlert/PluginData/Textures/flask_256.png new file mode 100644 index 0000000..2d97c9f Binary files /dev/null and b/GameData/ScienceAlert/PluginData/Textures/flask_256.png differ diff --git a/GameData/ScienceAlert/PluginData/Textures/flask_64.png b/GameData/ScienceAlert/PluginData/Textures/flask_64.png new file mode 100644 index 0000000..d5920d1 Binary files /dev/null and b/GameData/ScienceAlert/PluginData/Textures/flask_64.png differ diff --git a/GameData/ScienceAlert/PluginData/Textures/sheet-38-unused.png b/GameData/ScienceAlert/PluginData/Textures/sheet-38-unused.png new file mode 100644 index 0000000..d272a90 Binary files /dev/null and b/GameData/ScienceAlert/PluginData/Textures/sheet-38-unused.png differ diff --git a/GameData/ScienceAlert/PluginData/Textures/sheet-38.png b/GameData/ScienceAlert/PluginData/Textures/sheet-38.png new file mode 100644 index 0000000..77c4f58 Binary files /dev/null and b/GameData/ScienceAlert/PluginData/Textures/sheet-38.png differ diff --git a/Textures/sheet.png b/GameData/ScienceAlert/PluginData/Textures/sheet.png similarity index 100% rename from Textures/sheet.png rename to GameData/ScienceAlert/PluginData/Textures/sheet.png diff --git a/GameData/ScienceAlert/PluginData/profiles.cfg b/GameData/ScienceAlert/PluginData/profiles.cfg new file mode 100644 index 0000000..66312ba --- /dev/null +++ b/GameData/ScienceAlert/PluginData/profiles.cfg @@ -0,0 +1,1221 @@ +Stored_Profiles +{ + PROFILE + { + name = default + modified = False + scienceThreshold = 0 + NE_KEES_TEST + { + Enabled = True + SoundOnDiscovery = True + AnimationOnDiscovery = True + StopWarpOnDiscovery = False + Filter = Unresearched + IsDefault = False + } + NE_KEES_PPMD + { + Enabled = True + SoundOnDiscovery = True + AnimationOnDiscovery = True + StopWarpOnDiscovery = False + Filter = Unresearched + IsDefault = False + } + NE_KEES_ODC + { + Enabled = True + SoundOnDiscovery = True + AnimationOnDiscovery = True + StopWarpOnDiscovery = False + Filter = Unresearched + IsDefault = False + } + NE_KEES_POSA1 + { + Enabled = True + SoundOnDiscovery = True + AnimationOnDiscovery = True + StopWarpOnDiscovery = False + Filter = Unresearched + IsDefault = False + } + NE_KEES_POSA2 + { + Enabled = True + SoundOnDiscovery = True + AnimationOnDiscovery = True + StopWarpOnDiscovery = False + Filter = Unresearched + IsDefault = False + } + NE_KeminiTEST + { + Enabled = True + SoundOnDiscovery = True + AnimationOnDiscovery = True + StopWarpOnDiscovery = False + Filter = Unresearched + IsDefault = False + } + NE_KeminiD5 + { + Enabled = True + SoundOnDiscovery = True + AnimationOnDiscovery = True + StopWarpOnDiscovery = False + Filter = Unresearched + IsDefault = False + } + NE_KeminiD8 + { + Enabled = True + SoundOnDiscovery = True + AnimationOnDiscovery = True + StopWarpOnDiscovery = False + Filter = Unresearched + IsDefault = False + } + NE_KeminiMSC3 + { + Enabled = True + SoundOnDiscovery = True + AnimationOnDiscovery = True + StopWarpOnDiscovery = False + Filter = Unresearched + IsDefault = False + } + NE_KeminiD10 + { + Enabled = True + SoundOnDiscovery = True + AnimationOnDiscovery = True + StopWarpOnDiscovery = False + Filter = Unresearched + IsDefault = False + } + NE_KeminiD7 + { + Enabled = True + SoundOnDiscovery = True + AnimationOnDiscovery = True + StopWarpOnDiscovery = False + Filter = Unresearched + IsDefault = False + } + NE_ADUM + { + Enabled = True + SoundOnDiscovery = True + AnimationOnDiscovery = True + StopWarpOnDiscovery = False + Filter = Unresearched + IsDefault = False + } + NE_SpiU + { + Enabled = True + SoundOnDiscovery = True + AnimationOnDiscovery = True + StopWarpOnDiscovery = False + Filter = Unresearched + IsDefault = False + } + NE_Test + { + Enabled = True + SoundOnDiscovery = True + AnimationOnDiscovery = True + StopWarpOnDiscovery = False + Filter = Unresearched + IsDefault = False + } + NE_FLEX + { + Enabled = True + SoundOnDiscovery = True + AnimationOnDiscovery = True + StopWarpOnDiscovery = False + Filter = Unresearched + IsDefault = False + } + NE_CFI + { + Enabled = True + SoundOnDiscovery = True + AnimationOnDiscovery = True + StopWarpOnDiscovery = False + Filter = Unresearched + IsDefault = False + } + NE_CCF + { + Enabled = True + SoundOnDiscovery = True + AnimationOnDiscovery = True + StopWarpOnDiscovery = False + Filter = Unresearched + IsDefault = False + } + NE_CFE + { + Enabled = True + SoundOnDiscovery = True + AnimationOnDiscovery = True + StopWarpOnDiscovery = False + Filter = Unresearched + IsDefault = False + } + NE_MEE1 + { + Enabled = True + SoundOnDiscovery = True + AnimationOnDiscovery = True + StopWarpOnDiscovery = False + Filter = Unresearched + IsDefault = False + } + NE_MEE2 + { + Enabled = True + SoundOnDiscovery = True + AnimationOnDiscovery = True + StopWarpOnDiscovery = False + Filter = Unresearched + IsDefault = False + } + NE_MIS1 + { + Enabled = True + SoundOnDiscovery = True + AnimationOnDiscovery = True + StopWarpOnDiscovery = False + Filter = Unresearched + IsDefault = False + } + NE_MIS2 + { + Enabled = True + SoundOnDiscovery = True + AnimationOnDiscovery = True + StopWarpOnDiscovery = False + Filter = Unresearched + IsDefault = False + } + NE_MIS3 + { + Enabled = True + SoundOnDiscovery = True + AnimationOnDiscovery = True + StopWarpOnDiscovery = False + Filter = Unresearched + IsDefault = False + } + NE_CVB + { + Enabled = True + SoundOnDiscovery = True + AnimationOnDiscovery = True + StopWarpOnDiscovery = False + Filter = Unresearched + IsDefault = False + } + NE_PACE + { + Enabled = True + SoundOnDiscovery = True + AnimationOnDiscovery = True + StopWarpOnDiscovery = False + Filter = Unresearched + IsDefault = False + } + crewReport + { + Enabled = True + SoundOnDiscovery = True + AnimationOnDiscovery = True + StopWarpOnDiscovery = False + Filter = Unresearched + IsDefault = False + } + evaReport + { + Enabled = True + SoundOnDiscovery = True + AnimationOnDiscovery = True + StopWarpOnDiscovery = False + Filter = Unresearched + IsDefault = False + } + mysteryGoo + { + Enabled = True + SoundOnDiscovery = True + AnimationOnDiscovery = True + StopWarpOnDiscovery = False + Filter = Unresearched + IsDefault = False + } + surfaceSample + { + Enabled = True + SoundOnDiscovery = True + AnimationOnDiscovery = True + StopWarpOnDiscovery = False + Filter = Unresearched + IsDefault = False + } + mobileMaterialsLab + { + Enabled = True + SoundOnDiscovery = True + AnimationOnDiscovery = True + StopWarpOnDiscovery = False + Filter = Unresearched + IsDefault = False + } + temperatureScan + { + Enabled = True + SoundOnDiscovery = True + AnimationOnDiscovery = True + StopWarpOnDiscovery = False + Filter = Unresearched + IsDefault = False + } + barometerScan + { + Enabled = True + SoundOnDiscovery = True + AnimationOnDiscovery = True + StopWarpOnDiscovery = False + Filter = Unresearched + IsDefault = False + } + seismicScan + { + Enabled = True + SoundOnDiscovery = True + AnimationOnDiscovery = True + StopWarpOnDiscovery = False + Filter = Unresearched + IsDefault = False + } + gravityScan + { + Enabled = True + SoundOnDiscovery = True + AnimationOnDiscovery = True + StopWarpOnDiscovery = False + Filter = Unresearched + IsDefault = False + } + atmosphereAnalysis + { + Enabled = True + SoundOnDiscovery = True + AnimationOnDiscovery = True + StopWarpOnDiscovery = False + Filter = Unresearched + IsDefault = False + } + asteroidSample + { + Enabled = True + SoundOnDiscovery = True + AnimationOnDiscovery = True + StopWarpOnDiscovery = False + Filter = Unresearched + IsDefault = False + } + infraredTelescope + { + Enabled = True + SoundOnDiscovery = True + AnimationOnDiscovery = True + StopWarpOnDiscovery = False + Filter = Unresearched + IsDefault = False + } + recovery + { + Enabled = True + SoundOnDiscovery = True + AnimationOnDiscovery = True + StopWarpOnDiscovery = False + Filter = Unresearched + IsDefault = False + } + FASAmysteryGoo + { + Enabled = True + SoundOnDiscovery = True + AnimationOnDiscovery = True + StopWarpOnDiscovery = False + Filter = Unresearched + IsDefault = False + } + FASAlaserSurfaceSample + { + Enabled = True + SoundOnDiscovery = True + AnimationOnDiscovery = True + StopWarpOnDiscovery = False + Filter = Unresearched + IsDefault = False + } + GeminiVesselReport + { + Enabled = True + SoundOnDiscovery = True + AnimationOnDiscovery = True + StopWarpOnDiscovery = False + Filter = Unresearched + IsDefault = False + } + MercuryVesselReport + { + Enabled = True + SoundOnDiscovery = True + AnimationOnDiscovery = True + StopWarpOnDiscovery = False + Filter = Unresearched + IsDefault = False + } + magScan + { + Enabled = True + SoundOnDiscovery = True + AnimationOnDiscovery = True + StopWarpOnDiscovery = False + Filter = Unresearched + IsDefault = False + } + rpwsScan + { + Enabled = True + SoundOnDiscovery = True + AnimationOnDiscovery = True + StopWarpOnDiscovery = False + Filter = Unresearched + IsDefault = False + } + scopeScan + { + Enabled = True + SoundOnDiscovery = True + AnimationOnDiscovery = True + StopWarpOnDiscovery = False + Filter = Unresearched + IsDefault = False + } + dmImagingPlatform + { + Enabled = True + SoundOnDiscovery = True + AnimationOnDiscovery = True + StopWarpOnDiscovery = False + Filter = Unresearched + IsDefault = False + } + dmSIGINT + { + Enabled = True + SoundOnDiscovery = True + AnimationOnDiscovery = True + StopWarpOnDiscovery = False + Filter = Unresearched + IsDefault = False + } + dmReconScan + { + Enabled = True + SoundOnDiscovery = True + AnimationOnDiscovery = True + StopWarpOnDiscovery = False + Filter = Unresearched + IsDefault = False + } + dmNAlbedoScan + { + Enabled = True + SoundOnDiscovery = True + AnimationOnDiscovery = True + StopWarpOnDiscovery = False + Filter = Unresearched + IsDefault = False + } + dmXRayDiffract + { + Enabled = True + SoundOnDiscovery = True + AnimationOnDiscovery = True + StopWarpOnDiscovery = False + Filter = Unresearched + IsDefault = False + } + dmlaserblastscan + { + Enabled = True + SoundOnDiscovery = True + AnimationOnDiscovery = True + StopWarpOnDiscovery = False + Filter = Unresearched + IsDefault = False + } + dmSolarParticles + { + Enabled = True + SoundOnDiscovery = True + AnimationOnDiscovery = True + StopWarpOnDiscovery = False + Filter = Unresearched + IsDefault = False + } + dmSoilMoisture + { + Enabled = True + SoundOnDiscovery = True + AnimationOnDiscovery = True + StopWarpOnDiscovery = False + Filter = Unresearched + IsDefault = False + } + dmAsteroidScan + { + Enabled = True + SoundOnDiscovery = True + AnimationOnDiscovery = True + StopWarpOnDiscovery = False + Filter = Unresearched + IsDefault = False + } + dmRadiometerScan + { + Enabled = True + SoundOnDiscovery = True + AnimationOnDiscovery = True + StopWarpOnDiscovery = False + Filter = Unresearched + IsDefault = False + } + dmseismicHammer + { + Enabled = True + SoundOnDiscovery = True + AnimationOnDiscovery = True + StopWarpOnDiscovery = False + Filter = Unresearched + IsDefault = False + } + dmbathymetryscan + { + Enabled = True + SoundOnDiscovery = True + AnimationOnDiscovery = True + StopWarpOnDiscovery = False + Filter = Unresearched + IsDefault = False + } + dmbiodrillscan + { + Enabled = True + SoundOnDiscovery = True + AnimationOnDiscovery = True + StopWarpOnDiscovery = False + Filter = Unresearched + IsDefault = False + } + AnomalyScan + { + Enabled = True + SoundOnDiscovery = True + AnimationOnDiscovery = True + StopWarpOnDiscovery = False + Filter = Unresearched + IsDefault = False + } + TargetScanning + { + Enabled = True + SoundOnDiscovery = True + AnimationOnDiscovery = True + StopWarpOnDiscovery = False + Filter = Unresearched + IsDefault = False + } + GeigerCounter + { + Enabled = True + SoundOnDiscovery = True + AnimationOnDiscovery = True + StopWarpOnDiscovery = False + Filter = Unresearched + IsDefault = False + } + ApolloStatusReport + { + Enabled = True + SoundOnDiscovery = True + AnimationOnDiscovery = True + StopWarpOnDiscovery = False + Filter = Unresearched + IsDefault = False + } + LEMvesselReport + { + Enabled = True + SoundOnDiscovery = True + AnimationOnDiscovery = True + StopWarpOnDiscovery = False + Filter = Unresearched + IsDefault = False + } + LEMlandingReport + { + Enabled = True + SoundOnDiscovery = True + AnimationOnDiscovery = True + StopWarpOnDiscovery = False + Filter = Unresearched + IsDefault = False + } + ALSEPenvironmentScan + { + Enabled = True + SoundOnDiscovery = True + AnimationOnDiscovery = True + StopWarpOnDiscovery = False + Filter = Unresearched + IsDefault = False + } + ALSEPsurfaceScan + { + Enabled = True + SoundOnDiscovery = True + AnimationOnDiscovery = True + StopWarpOnDiscovery = False + Filter = Unresearched + IsDefault = False + } + ALSEPreflectorExp + { + Enabled = True + SoundOnDiscovery = True + AnimationOnDiscovery = True + StopWarpOnDiscovery = False + Filter = Unresearched + IsDefault = False + } + } + PROFILE + { + name = test + modified = False + scienceThreshold = 0 + NE_KEES_TEST + { + Enabled = True + SoundOnDiscovery = True + AnimationOnDiscovery = True + StopWarpOnDiscovery = False + Filter = Unresearched + IsDefault = False + } + NE_KEES_PPMD + { + Enabled = True + SoundOnDiscovery = True + AnimationOnDiscovery = True + StopWarpOnDiscovery = False + Filter = Unresearched + IsDefault = False + } + NE_KEES_ODC + { + Enabled = True + SoundOnDiscovery = True + AnimationOnDiscovery = True + StopWarpOnDiscovery = False + Filter = Unresearched + IsDefault = False + } + NE_KEES_POSA1 + { + Enabled = True + SoundOnDiscovery = True + AnimationOnDiscovery = True + StopWarpOnDiscovery = False + Filter = Unresearched + IsDefault = False + } + NE_KEES_POSA2 + { + Enabled = True + SoundOnDiscovery = True + AnimationOnDiscovery = True + StopWarpOnDiscovery = False + Filter = Unresearched + IsDefault = False + } + NE_KeminiTEST + { + Enabled = True + SoundOnDiscovery = True + AnimationOnDiscovery = True + StopWarpOnDiscovery = False + Filter = Unresearched + IsDefault = False + } + NE_KeminiD5 + { + Enabled = True + SoundOnDiscovery = True + AnimationOnDiscovery = True + StopWarpOnDiscovery = False + Filter = Unresearched + IsDefault = False + } + NE_KeminiD8 + { + Enabled = True + SoundOnDiscovery = True + AnimationOnDiscovery = True + StopWarpOnDiscovery = False + Filter = Unresearched + IsDefault = False + } + NE_KeminiMSC3 + { + Enabled = True + SoundOnDiscovery = True + AnimationOnDiscovery = True + StopWarpOnDiscovery = False + Filter = Unresearched + IsDefault = False + } + NE_KeminiD10 + { + Enabled = True + SoundOnDiscovery = True + AnimationOnDiscovery = True + StopWarpOnDiscovery = False + Filter = Unresearched + IsDefault = False + } + NE_KeminiD7 + { + Enabled = True + SoundOnDiscovery = True + AnimationOnDiscovery = True + StopWarpOnDiscovery = False + Filter = Unresearched + IsDefault = False + } + NE_ADUM + { + Enabled = True + SoundOnDiscovery = True + AnimationOnDiscovery = True + StopWarpOnDiscovery = False + Filter = Unresearched + IsDefault = False + } + NE_SpiU + { + Enabled = True + SoundOnDiscovery = True + AnimationOnDiscovery = True + StopWarpOnDiscovery = False + Filter = Unresearched + IsDefault = False + } + NE_Test + { + Enabled = True + SoundOnDiscovery = True + AnimationOnDiscovery = True + StopWarpOnDiscovery = False + Filter = Unresearched + IsDefault = False + } + NE_FLEX + { + Enabled = True + SoundOnDiscovery = True + AnimationOnDiscovery = True + StopWarpOnDiscovery = False + Filter = Unresearched + IsDefault = False + } + NE_CFI + { + Enabled = True + SoundOnDiscovery = True + AnimationOnDiscovery = True + StopWarpOnDiscovery = False + Filter = Unresearched + IsDefault = False + } + NE_CCF + { + Enabled = True + SoundOnDiscovery = True + AnimationOnDiscovery = True + StopWarpOnDiscovery = False + Filter = Unresearched + IsDefault = False + } + NE_CFE + { + Enabled = True + SoundOnDiscovery = True + AnimationOnDiscovery = True + StopWarpOnDiscovery = False + Filter = Unresearched + IsDefault = False + } + NE_MEE1 + { + Enabled = True + SoundOnDiscovery = True + AnimationOnDiscovery = True + StopWarpOnDiscovery = False + Filter = Unresearched + IsDefault = False + } + NE_MEE2 + { + Enabled = True + SoundOnDiscovery = True + AnimationOnDiscovery = True + StopWarpOnDiscovery = False + Filter = Unresearched + IsDefault = False + } + NE_MIS1 + { + Enabled = True + SoundOnDiscovery = True + AnimationOnDiscovery = True + StopWarpOnDiscovery = False + Filter = Unresearched + IsDefault = False + } + NE_MIS2 + { + Enabled = True + SoundOnDiscovery = True + AnimationOnDiscovery = True + StopWarpOnDiscovery = False + Filter = Unresearched + IsDefault = False + } + NE_MIS3 + { + Enabled = True + SoundOnDiscovery = True + AnimationOnDiscovery = True + StopWarpOnDiscovery = False + Filter = Unresearched + IsDefault = False + } + NE_CVB + { + Enabled = True + SoundOnDiscovery = True + AnimationOnDiscovery = True + StopWarpOnDiscovery = False + Filter = Unresearched + IsDefault = False + } + NE_PACE + { + Enabled = True + SoundOnDiscovery = True + AnimationOnDiscovery = True + StopWarpOnDiscovery = False + Filter = Unresearched + IsDefault = False + } + crewReport + { + Enabled = True + SoundOnDiscovery = True + AnimationOnDiscovery = True + StopWarpOnDiscovery = False + Filter = Unresearched + IsDefault = False + } + evaReport + { + Enabled = True + SoundOnDiscovery = True + AnimationOnDiscovery = True + StopWarpOnDiscovery = False + Filter = Unresearched + IsDefault = False + } + mysteryGoo + { + Enabled = True + SoundOnDiscovery = True + AnimationOnDiscovery = True + StopWarpOnDiscovery = False + Filter = Unresearched + IsDefault = False + } + surfaceSample + { + Enabled = True + SoundOnDiscovery = True + AnimationOnDiscovery = True + StopWarpOnDiscovery = False + Filter = Unresearched + IsDefault = False + } + mobileMaterialsLab + { + Enabled = True + SoundOnDiscovery = True + AnimationOnDiscovery = True + StopWarpOnDiscovery = False + Filter = Unresearched + IsDefault = False + } + temperatureScan + { + Enabled = True + SoundOnDiscovery = True + AnimationOnDiscovery = True + StopWarpOnDiscovery = False + Filter = Unresearched + IsDefault = False + } + barometerScan + { + Enabled = True + SoundOnDiscovery = True + AnimationOnDiscovery = True + StopWarpOnDiscovery = False + Filter = Unresearched + IsDefault = False + } + seismicScan + { + Enabled = True + SoundOnDiscovery = True + AnimationOnDiscovery = True + StopWarpOnDiscovery = False + Filter = Unresearched + IsDefault = False + } + gravityScan + { + Enabled = True + SoundOnDiscovery = True + AnimationOnDiscovery = True + StopWarpOnDiscovery = False + Filter = Unresearched + IsDefault = False + } + atmosphereAnalysis + { + Enabled = True + SoundOnDiscovery = True + AnimationOnDiscovery = True + StopWarpOnDiscovery = False + Filter = Unresearched + IsDefault = False + } + asteroidSample + { + Enabled = True + SoundOnDiscovery = True + AnimationOnDiscovery = True + StopWarpOnDiscovery = False + Filter = Unresearched + IsDefault = False + } + infraredTelescope + { + Enabled = True + SoundOnDiscovery = True + AnimationOnDiscovery = True + StopWarpOnDiscovery = False + Filter = Unresearched + IsDefault = False + } + recovery + { + Enabled = True + SoundOnDiscovery = True + AnimationOnDiscovery = True + StopWarpOnDiscovery = False + Filter = Unresearched + IsDefault = False + } + FASAmysteryGoo + { + Enabled = True + SoundOnDiscovery = True + AnimationOnDiscovery = True + StopWarpOnDiscovery = False + Filter = Unresearched + IsDefault = False + } + FASAlaserSurfaceSample + { + Enabled = True + SoundOnDiscovery = True + AnimationOnDiscovery = True + StopWarpOnDiscovery = False + Filter = Unresearched + IsDefault = False + } + GeminiVesselReport + { + Enabled = True + SoundOnDiscovery = True + AnimationOnDiscovery = True + StopWarpOnDiscovery = False + Filter = Unresearched + IsDefault = False + } + MercuryVesselReport + { + Enabled = True + SoundOnDiscovery = True + AnimationOnDiscovery = True + StopWarpOnDiscovery = False + Filter = Unresearched + IsDefault = False + } + magScan + { + Enabled = True + SoundOnDiscovery = True + AnimationOnDiscovery = True + StopWarpOnDiscovery = False + Filter = Unresearched + IsDefault = False + } + rpwsScan + { + Enabled = True + SoundOnDiscovery = True + AnimationOnDiscovery = True + StopWarpOnDiscovery = False + Filter = Unresearched + IsDefault = False + } + scopeScan + { + Enabled = True + SoundOnDiscovery = True + AnimationOnDiscovery = True + StopWarpOnDiscovery = False + Filter = Unresearched + IsDefault = False + } + dmImagingPlatform + { + Enabled = True + SoundOnDiscovery = True + AnimationOnDiscovery = True + StopWarpOnDiscovery = False + Filter = Unresearched + IsDefault = False + } + dmSIGINT + { + Enabled = True + SoundOnDiscovery = True + AnimationOnDiscovery = True + StopWarpOnDiscovery = False + Filter = Unresearched + IsDefault = False + } + dmReconScan + { + Enabled = True + SoundOnDiscovery = True + AnimationOnDiscovery = True + StopWarpOnDiscovery = False + Filter = Unresearched + IsDefault = False + } + dmNAlbedoScan + { + Enabled = True + SoundOnDiscovery = True + AnimationOnDiscovery = True + StopWarpOnDiscovery = False + Filter = Unresearched + IsDefault = False + } + dmXRayDiffract + { + Enabled = True + SoundOnDiscovery = True + AnimationOnDiscovery = True + StopWarpOnDiscovery = False + Filter = Unresearched + IsDefault = False + } + dmlaserblastscan + { + Enabled = True + SoundOnDiscovery = True + AnimationOnDiscovery = True + StopWarpOnDiscovery = False + Filter = Unresearched + IsDefault = False + } + dmSolarParticles + { + Enabled = True + SoundOnDiscovery = True + AnimationOnDiscovery = True + StopWarpOnDiscovery = False + Filter = Unresearched + IsDefault = False + } + dmSoilMoisture + { + Enabled = True + SoundOnDiscovery = True + AnimationOnDiscovery = True + StopWarpOnDiscovery = False + Filter = Unresearched + IsDefault = False + } + dmAsteroidScan + { + Enabled = True + SoundOnDiscovery = True + AnimationOnDiscovery = True + StopWarpOnDiscovery = False + Filter = Unresearched + IsDefault = False + } + dmRadiometerScan + { + Enabled = True + SoundOnDiscovery = True + AnimationOnDiscovery = True + StopWarpOnDiscovery = False + Filter = Unresearched + IsDefault = False + } + dmseismicHammer + { + Enabled = True + SoundOnDiscovery = True + AnimationOnDiscovery = True + StopWarpOnDiscovery = False + Filter = Unresearched + IsDefault = False + } + dmbathymetryscan + { + Enabled = True + SoundOnDiscovery = True + AnimationOnDiscovery = True + StopWarpOnDiscovery = False + Filter = Unresearched + IsDefault = False + } + dmbiodrillscan + { + Enabled = True + SoundOnDiscovery = True + AnimationOnDiscovery = True + StopWarpOnDiscovery = False + Filter = Unresearched + IsDefault = False + } + AnomalyScan + { + Enabled = True + SoundOnDiscovery = True + AnimationOnDiscovery = True + StopWarpOnDiscovery = False + Filter = Unresearched + IsDefault = False + } + TargetScanning + { + Enabled = True + SoundOnDiscovery = True + AnimationOnDiscovery = True + StopWarpOnDiscovery = False + Filter = Unresearched + IsDefault = False + } + GeigerCounter + { + Enabled = True + SoundOnDiscovery = True + AnimationOnDiscovery = True + StopWarpOnDiscovery = False + Filter = Unresearched + IsDefault = False + } + ApolloStatusReport + { + Enabled = True + SoundOnDiscovery = True + AnimationOnDiscovery = True + StopWarpOnDiscovery = False + Filter = Unresearched + IsDefault = False + } + LEMvesselReport + { + Enabled = True + SoundOnDiscovery = True + AnimationOnDiscovery = True + StopWarpOnDiscovery = False + Filter = Unresearched + IsDefault = False + } + LEMlandingReport + { + Enabled = True + SoundOnDiscovery = True + AnimationOnDiscovery = True + StopWarpOnDiscovery = False + Filter = Unresearched + IsDefault = False + } + ALSEPenvironmentScan + { + Enabled = True + SoundOnDiscovery = True + AnimationOnDiscovery = True + StopWarpOnDiscovery = False + Filter = Unresearched + IsDefault = False + } + ALSEPsurfaceScan + { + Enabled = True + SoundOnDiscovery = True + AnimationOnDiscovery = True + StopWarpOnDiscovery = False + Filter = Unresearched + IsDefault = False + } + ALSEPreflectorExp + { + Enabled = True + SoundOnDiscovery = True + AnimationOnDiscovery = True + StopWarpOnDiscovery = True + Filter = Unresearched + IsDefault = False + } + } +} diff --git a/GameData/ScienceAlert/PluginData/settings.cfg b/GameData/ScienceAlert/PluginData/settings.cfg new file mode 100644 index 0000000..8320f0e --- /dev/null +++ b/GameData/ScienceAlert/PluginData/settings.cfg @@ -0,0 +1,63 @@ +DebugMode = False +TimeWarpCheckThreshold = 5 +WindowOpacity = 255 +EvaReportOnTop = True +ScanInterfaceType = None +ToolbarInterfaceType = BlizzyToolbar +additional +{ + config + { + OptionsWindow + { + WindowX = 1239 + WindowY = 122 + Draggable = True + Visible = False + } + ExperimentWindow + { + WindowX = 813 + WindowY = 95 + Draggable = True + Visible = False + } + DebugWindow + { + WindowX = 840 + WindowY = 525 + Draggable = True + Visible = False + } + } +} +General +{ + GlobalWarp = ByExperiment + SoundNotification = ByExperiment + EvaAtmospherePressureWarnThreshold = 0.00035 + EvaAtmosphereVelocityWarnThreshold = 30 +} +UserInterface +{ + ShowReportValue = True + DisplayCurrentBiome = False + FlaskAnimationEnabled = True + StarFlaskFrameRate = 24 +} +CrewedVesselSettings +{ + CheckSurfaceSampleNotEva = False +} +LogSettings +{ + LogMask = 0 + // Bit index = message type + // Bit 0 = None + // Bit 1 = Normal + // Bit 2 = Debug + // Bit 3 = Verbose + // Bit 4 = Performance + // Bit 5 = Warning + // Bit 6 = Error +} diff --git a/GameData/ScienceAlert/Plugins/ScienceAlert.pdb b/GameData/ScienceAlert/Plugins/ScienceAlert.pdb new file mode 100644 index 0000000..ef19a44 Binary files /dev/null and b/GameData/ScienceAlert/Plugins/ScienceAlert.pdb differ diff --git a/GameData/ScienceAlert/README.md b/GameData/ScienceAlert/README.md new file mode 100644 index 0000000..bdc94a6 --- /dev/null +++ b/GameData/ScienceAlert/README.md @@ -0,0 +1,11 @@ +# ScienceAlert Updated + +This is a minor updated version of the Kerbal Space Program mod ScienceAlert (see original here: https://github.com/DennyTX/ScienceAlert) to fix a few bugs. + + +TODO + +Significant FPS drop + + +I just got this, and started a new game. When I go on EVA and get an EVA report, then hit keep, it doesnt show it for review on my kerbal or when I get back to my capsule. Transmitting data does not seem to work even when having an external antenna. \ No newline at end of file diff --git a/GameData/ScienceAlert/ScienceAlert.version b/GameData/ScienceAlert/ScienceAlert.version new file mode 100644 index 0000000..45c2002 --- /dev/null +++ b/GameData/ScienceAlert/ScienceAlert.version @@ -0,0 +1,30 @@ +{ + "NAME": "ScienceAlert", + "URL": "https://raw.githubusercontent.com/linuxgurugamer/ScienceAlert/refs/heads/master/ScienceAlert.version", + "DOWNLOAD": "https://spacedock.info/mod/1886/ScienceAlert%20ReAlerted", + "GITHUB": { + "USERNAME": "linuxgurugamer", + "REPOSITORY": "ScienceAlert" + }, + "VERSION": { + "MAJOR": 1, + "MINOR": 9, + "PATCH": 20, + "BUILD": 5 + }, + "KSP_VERSION": { + "MAJOR": 1, + "MINOR": 12, + "PATCH": 5 + }, + "KSP_VERSION_MIN": { + "MAJOR": 1, + "MINOR": 9, + "PATCH": 0 + }, + "KSP_VERSION_MAX": { + "MAJOR": 1, + "MINOR": 12, + "PATCH": 99 + } +} diff --git a/GameData/ScienceAlert/ScienceAlert.version.1-12-3 b/GameData/ScienceAlert/ScienceAlert.version.1-12-3 new file mode 100644 index 0000000..9a45381 --- /dev/null +++ b/GameData/ScienceAlert/ScienceAlert.version.1-12-3 @@ -0,0 +1,25 @@ +{ + "NAME": "ScienceAlert", + "URL": "http://ksp.spacetux.net/avc/ScienceAlert", + "DOWNLOAD": "https://spacedock.info/mod/1886/ScienceAlert%20ReAlerted", + "GITHUB": { + "USERNAME": "linuxgurugamer", + "REPOSITORY": "ScienceAlert" + }, + "VERSION": { + "MAJOR": 1, + "MINOR": 9, + "PATCH": 10, + "BUILD": 1 + }, + "KSP_VERSION": { + "MAJOR": 1, + "MINOR": 12, + "PATCH": 3 + }, + "KSP_VERSION_MIN": { + "MAJOR": 1, + "MINOR": 9, + "PATCH": 0 + } +} diff --git a/GameData/ScienceAlert/sounds/bubbles.wav b/GameData/ScienceAlert/sounds/bubbles.wav new file mode 100644 index 0000000..1db0c67 Binary files /dev/null and b/GameData/ScienceAlert/sounds/bubbles.wav differ diff --git a/GameData/ScienceAlert/sounds/click1.wav b/GameData/ScienceAlert/sounds/click1.wav new file mode 100644 index 0000000..8046128 Binary files /dev/null and b/GameData/ScienceAlert/sounds/click1.wav differ diff --git a/GameData/ScienceAlert/sounds/click2.wav b/GameData/ScienceAlert/sounds/click2.wav new file mode 100644 index 0000000..332d4f2 Binary files /dev/null and b/GameData/ScienceAlert/sounds/click2.wav differ diff --git a/GameData/ScienceAlert/sounds/error.wav b/GameData/ScienceAlert/sounds/error.wav new file mode 100644 index 0000000..4720816 Binary files /dev/null and b/GameData/ScienceAlert/sounds/error.wav differ diff --git a/Instructions.txt b/Instructions.txt new file mode 100644 index 0000000..cd6dee0 --- /dev/null +++ b/Instructions.txt @@ -0,0 +1,133 @@ +These scripts are based on the following directory layout: + +modmaindir + |-> GameData + |-> ModDir + |-> Plugins + |-> Sourcedir + +The GameData should contain all files needed for a release + +Overview and Dependencies + + Dependencies + These instructions are based on using Microsoft Visual Studio 2017. + + The 7Zip packaging program is required for the final packaging, + available here: http://www.7-zip.org/ + + The JQ program is required to parse the JSON version file, available + here: https://stedolan.github.io/jq/download/ + + Overview + + Part of the process involves adding a file, AssemblyVersion.tt, to the + codebase. This file is called a "Text Template", and is used to create + the AssemblyVersion value which is then stored in the final DLL + + There are two batch files, deploy.bat and buildRelease.bat. The + "deploy.bat" used to copy the DLL to the GameData directory, and + then to copy the complete release to a test install. The + "buildRelease.bat" is used to create the final zip file for release + +While the packaging program can be changed to an alternative (with appropriate +changes to the "buildRelease.bat" file, the JQ program is required and not +replacable. + +The assumption is also made that the mod is downloaded and ready to be worked +on. + + + +Instructions + +Create the GameData folder if it doesn't exist + + If it doesn't exist, then copy the latest release of the mod into the GameData folder + +Find the .version file (if it exists) and copy it to the top level directory + + +You can either copy the AssemblyVersion.tt to the main mod folder, or +create a new TextTemplate in the project (using MS Studio) and do a copy/paste +operation. If you copy it over, you will need to add the file to the project +in the IDE + +Edit the AssemblyVersion.tt file, update the following line: + + string versionfile = @"CompletePathTo.version"; + +Replace the "CompletePathTo.version" with the path to the .version file. + +Remove or comment out the following line from the file AssemblyInfo.cs (usually +located in the "Properties" folder inside your C# project): + + [assembly: AssemblyVersion("1.0.0.0")] + + +Add the following to the Pre-build event command line. This line is based on +using Visual Studio 2017, installed on the X drive: + + + set textTemplatingPath="%ProgramFiles(x86)%\Microsoft Visual Studio\2017\Community\Common7\IDE\texttransform.exe" + + %textTemplatingPath% "$(ProjectDir)AssemblyVersion.tt" + + +Copy the two files, "deploy.bat" and "buildRelease.bat" to the main mod folder + +You need to update the deploy.bat and the buildRelease.bat with the following values: + + +H to point to your test KSP install: set H=R:\KSP_1.3.1_dev +GAMEDIR to the name of the mod folder: set GAMEDIR=xxxxx +GAMEDATA to point to the GameData folder: GAMEDATA="GameData" +VERSIONFILE to the name of the .version file: VERSIONFILE=%GAMEDIR%.version + +In most cases, the VERSIONFILE is built from the GAMEDIR, but some mods use a +different name + +For the buildRelease.bat, you have to update the following as well: + +If existing, LICENSE to the license file: set LICENSE=License.txt +If existing, README to the Readme file: README=ReadMe.md +Set RELEASEDIR to where you want the zip file: RELEASEDIR=d:\Users\jbb\release + +If you want to use a different program than 7z, change it in the next line. +If you do, you will have to change the options to the zip program at the end +of the file: + +set ZIP="c:\Program Files\7-zip\7z.exe" + + + +In the MS VS IDE, right-click on the Solution in the Solution Explorer, and +select "Add -> New Solution Folder", give the folder a name "SolutionItems" + +Right-click on the SolutionItems folder, and select "Add -> Existing item...", +add the two files just copied and the .version file + + + +Add the following to the Post-build event command line, replace the "xxxxx" +with the complete path to the top level directory of the mod: + + + start /D xxxxx /WAIT deploy.bat $(TargetDir) $(TargetFileName) + + if $(ConfigurationName) == Release ( + + start /D xxxxx /WAIT buildRelease.bat $(TargetDir) $(TargetFileName) + + ) + +Make sure the .version file has the correct values. + +Now, set the Visual Studio configuration to Debug, and do a test compile. + +If all is correct, it will compile the code, copy the DLL to the destination +folder, and then copy the complete release to your test KSP installation + +Finally, change the VS config to "Release" and rebuild the mod. This time it +will add the step to pack up the entire release and leave it in the RELEASEDIR + diff --git a/README.md b/README.md index 5a865b5..bdc94a6 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,11 @@ -# ScienceAlert -When is is it time for science?   +# ScienceAlert Updated + +This is a minor updated version of the Kerbal Space Program mod ScienceAlert (see original here: https://github.com/DennyTX/ScienceAlert) to fix a few bugs. + + +TODO + +Significant FPS drop + + +I just got this, and started a new game. When I go on EVA and get an EVA report, then hit keep, it doesnt show it for review on my kerbal or when I get back to my capsule. Transmitting data does not seem to work even when having an external antenna. \ No newline at end of file diff --git a/ReeperCommon/AudioPlayer.cs b/ReeperCommon/AudioPlayer.cs deleted file mode 100644 index 8604a92..0000000 --- a/ReeperCommon/AudioPlayer.cs +++ /dev/null @@ -1,140 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using UnityEngine; - -namespace ReeperCommon -{ - internal class AudioPlayer : MonoBehaviour - { - private static AudioPlayer _instance; - private readonly Dictionary sounds = new Dictionary(); - private AudioSource source; - - public static AudioPlayer Audio - { - get - { - if (_instance != null) return _instance; - GameObject gameObject = new GameObject("Reeper.AudioPlayer", typeof(AudioSource)); - gameObject.AddComponent().SetSource(gameObject); - return _instance; - } - } - - public int Count => sounds.Count; - - private void Awake() - { - if (_instance == null) - { - _instance = this; - } - } - - private void OnDestroy() - { - if (_instance == this) - { - _instance = null; - } - } - - public void SetSource(GameObject src, bool b2d = true) - { - source = src.GetComponent() ?? src.AddComponent(); - } - - public int LoadSoundsFrom(string dir, bool b2D = true) - { - int counter = 0; - if (System.IO.Path.IsPathRooted(dir) && System.IO.Directory.Exists(dir)) - { - dir = System.IO.Path.GetFullPath(dir).Replace('\\', '/'); - dir = ConfigUtil.GetRelativeToGameData(dir); - } - else - { - dir = dir.TrimStart('\\', '/'); - if (!System.IO.Directory.Exists(System.IO.Path.Combine(System.IO.Path.GetFullPath(KSPUtil.ApplicationRootPath + "GameData"), dir))) - { - string text = System.IO.Path.Combine(ConfigUtil.GetDllDirectoryPath(), dir); - if (!System.IO.Directory.Exists(text)) - { - Log.Debug("[ScienceAlert]:AudioPlayer: Couldn't find '{0}'", dir); - return 0; - } - dir = ConfigUtil.GetRelativeToGameData(text).Replace('\\', '/'); - } - else - { - dir = dir.Replace('\\', '/'); - } - } - GameDatabase.Instance.databaseAudio.ForEach(delegate(AudioClip ac) - { - string text2 = ac.name; - int num = text2.LastIndexOf('/'); - if (num >= 0) - { - text2 = text2.Substring(0, num); - } - if (!string.Equals(text2, dir)) return; - if (sounds.ContainsKey(ac.name))return; - sounds.Add(ac.name, new PlayableSound(ac)); - counter++; - }); - if (counter == 0) - { - Log.Warning("AudioPlayer: Didn't load any sounds from directory '{0}'", dir); - } - return counter; - } - - public bool PlayThenDelay(string name, float delay = 1f) - { - return Play(name, 1f, delay); - } - - public bool PlayUI(string name, float delay = 0f) - { - return Play(name, GameSettings.UI_VOLUME, delay); - } - - public bool Play(string name, float volume = 1f, float delay = 0f) - { - PlayableSound playableSound = null; - if (sounds.ContainsKey(name)) - { - playableSound = sounds[name]; - } - else - { - string text = sounds.Keys.ToList().SingleOrDefault((string k) => string.Equals(PlayableSound.GetShortName(k), name)); - if (!string.IsNullOrEmpty(text) && sounds.ContainsKey(text)) - { - playableSound = sounds[text]; - } - } - if (playableSound == null) - { - return false; - } - if (!(Time.realtimeSinceStartup - playableSound.nextPlayableTime > 0f)) return false; - if (source == null) - { - SetSource(gameObject); - } - try - { - source.PlayOneShot(playableSound.clip, Mathf.Clamp(volume, 0f, 1f)); - playableSound.nextPlayableTime = Time.realtimeSinceStartup + delay; - bool result = true; - return result; - } - catch (System.Exception) - { - return false; - } - } - } -} diff --git a/ReeperCommon/ConfigNodeTypeHandler.cs b/ReeperCommon/ConfigNodeTypeHandler.cs deleted file mode 100644 index e3d5535..0000000 --- a/ReeperCommon/ConfigNodeTypeHandler.cs +++ /dev/null @@ -1,127 +0,0 @@ -using UnityEngine; - -namespace ReeperCommon -{ - [System.AttributeUsage(System.AttributeTargets.Property | System.AttributeTargets.Field | System.AttributeTargets.Event)] - internal class DoNotSerialize : System.Attribute - {} - - internal interface IConfigNodeTypeFormatter - { - string Serialize(object obj); - - object Deserialize(object obj, string value); - } - - internal interface IReeperSerializable - { - void OnSerialize(ConfigNode node); - - void OnDeserialize(ConfigNode node); - } - - internal class ConfigNodeTypeHandler - { - internal class Vector2Formatter : IConfigNodeTypeFormatter - { - public string Serialize(object obj) - { - return KSPUtil.WriteVector((Vector2)obj); - } - - public object Deserialize(object obj, string value) - { - Vector2 vector = (Vector2)obj; - vector = KSPUtil.ParseVector2(value); - return vector; - } - } - - private System.Collections.Generic.Dictionary handlers = new System.Collections.Generic.Dictionary(); - - internal ConfigNodeTypeHandler() - { - AddFormatter(typeof(Vector2), typeof(Vector2Formatter)); - } - - internal void AddFormatter(System.Type targetType, IConfigNodeTypeFormatter impl) - { - if (handlers.ContainsKey(targetType)) - { - handlers[targetType] = impl; - return; - } - handlers.Add(targetType, impl); - } - - internal void AddFormatter(System.Type targetType, System.Type formatter) - { - try - { - if (!typeof(IConfigNodeTypeFormatter).IsAssignableFrom(formatter)) return; - IConfigNodeTypeFormatter value = (IConfigNodeTypeFormatter)System.Activator.CreateInstance(formatter); - if (handlers.ContainsKey(targetType)) - handlers[targetType] = value; - else - handlers.Add(targetType, value); - } - catch (System.Exception ex) - { - Log.Debug("[ScienceAlert]:ConfigNodeTypeHandler.AddFormatter: Exception while attempting to add handler for type '{0}' (of type {1}): {2}", targetType.FullName, formatter.FullName, ex); - } - } - - internal string Serialize(ref T obj) - { - System.Type typeFromHandle = typeof(T); - if (handlers.ContainsKey(typeFromHandle)) - { - IConfigNodeTypeFormatter configNodeTypeFormatter = handlers[typeFromHandle]; - return configNodeTypeFormatter.Serialize(obj); - } - if (typeFromHandle.IsEnum) - { - return obj.ToString(); - } - return obj.ToString(); - } - - internal bool Deserialize(ref T obj, string value) - { - if (!handlers.ContainsKey(typeof(T))) - { - bool result; - if (typeof(T).IsEnum) - { - try - { - obj = (T)System.Enum.Parse(typeof(T), value, true); - return true; - } - catch (System.Exception) - { - return false; - } - } - try - { - obj = ConfigUtil.ParseThrowable(value); - result = true; - } - catch (System.Exception) - { - result = false; - } - return result; - } - - object obj2 = handlers[typeof(T)].Deserialize(obj, value); - if (obj2 != null) - { - obj = (T)obj2; - return true; - } - return false; - } - } -} diff --git a/ReeperCommon/ConfigUtil.cs b/ReeperCommon/ConfigUtil.cs deleted file mode 100644 index 4733322..0000000 --- a/ReeperCommon/ConfigUtil.cs +++ /dev/null @@ -1,215 +0,0 @@ -using System; -using System.Reflection; -using UnityEngine; - -namespace ReeperCommon -{ - public static class ConfigUtil - { - public static T ParseEnum(this ConfigNode node, string valueName, T defaultValue) - { - try - { - string value = node.GetValue(valueName); - T result; - if (string.IsNullOrEmpty(value)) - { - result = defaultValue; - return result; - } - Enum.GetValues(typeof(T)); - result = (T)Enum.Parse(typeof(T), value, true); - return result; - } - catch (Exception ex) - { - Log.Debug("[ScienceAlert]:Settings: Failed to parse value '{0}' from ConfigNode, resulted in an exception {1}", valueName, ex); - } - return defaultValue; - } - - public static string Parse(this ConfigNode node, string valueName, string defaultValue = "") - { - try - { - string result; - if (!node.HasValue(valueName)) - { - result = defaultValue; - return result; - } - result = node.GetValue(valueName); - return result; - } - catch (Exception ex) - { - Log.Debug("[ScienceAlert]:Settings: Failed to parse string value '{0}' from ConfigNode, resulted in an exception {1}", valueName, ex); - } - return defaultValue; - } - - public static T Parse(string value) - { - return Parse(value, default(T)); - } - - public static T Parse(string value, T defaultValue) - { - try - { - MethodInfo method = typeof(T).GetMethod("TryParse", new[] - { - typeof(string), - typeof(T).MakeByRefType() - }); - if (method == null) - { - Log.Debug("[ScienceAlert]:Failed to locate TryParse in {0}", typeof(T).FullName); - } - else - { - object[] array = { - value, - default(T) - }; - T result; - if ((bool)method.Invoke(null, array)) - { - result = (T)array[1]; - return result; - } - result = defaultValue; - return result; - } - } - catch (Exception) - { - T result = defaultValue; - return result; - } - return defaultValue; - } - - public static T ParseThrowable(string value) - { - T result; - try - { - MethodInfo method = typeof(T).GetMethod("TryParse", new[] - { - typeof(string), - typeof(T).MakeByRefType() - }); - if (method == null) - { - throw new Exception("TryParse method not found"); - } - object[] array = { - value, - default(T) - }; - if (!(bool)method.Invoke(null, array)) - { - throw new Exception("TryParse invoke reports failure"); - } - result = (T)array[1]; - } - catch (Exception ex) - { - Log.Debug("[ScienceAlert]:ConfigUtil.Parse<{0}>: Failed to parse from value '{1}': {2}", typeof(T).FullName, value, ex); - throw; - } - return result; - } - - public static T Parse(this ConfigNode node, string valueName, T defaultValue) - { - try - { - T result; - if (!node.HasValue(valueName)) - { - result = defaultValue; - return result; - } - string value = node.GetValue(valueName); - result = Parse(value, defaultValue); - return result; - } - catch (Exception ex) - { - Log.Debug("[ScienceAlert]:ConfigUtil.Parse<{0}>: Exception while parsing a value named {1}: {2}", typeof(T).FullName, valueName, ex); - } - return defaultValue; - } - - public static string ReadString(this ConfigNode node, string valueName, string defaultValue = "") - { - if (node == null || !node.HasValue(valueName)) - { - return defaultValue; - } - return node.GetValue(valueName); - } - - public static void Set(this ConfigNode node, string valueName, string value) - { - if (node.HasValue(valueName)) - { - node.SetValue(valueName, value); - return; - } - node.AddValue(valueName, value); - } - - public static void Set(this ConfigNode node, string valueName, T value) - { - node.Set(valueName, value.ToString()); - } - - public static string GetDllDirectoryPath() - { - return System.IO.Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); - } - - public static string GetRelativeToGameData(string path) - { - if (!path.Contains("GameData")) - { - Log.Debug( - $"GetRelativeToGameData: Given path '{path}' does not reside in GameData. The plugin does not appear to be installed correctly."); - throw new FormatException($"GetRelativeToGameData: path '{path}' does not contain 'GameData'"); - } - int num = path.IndexOf("GameData"); - string text = ""; - if (path.Length > num + "GameData".Length + 1) - { - text = path.Substring(num + "GameData".Length + 1); - } - return text; - } - - public static Rect ReadRect(this ConfigNode node, string name, Rect defaultValue = default(Rect)) - { - if (node.HasValue(name)) - { - try - { - Vector4 vector = KSPUtil.ParseVector4(node.GetValue(name)); - return new Rect(vector.x, vector.y, vector.z, vector.w); - } - catch (Exception ex) - { - Log.Debug("[ScienceAlert]:ConfigUtil.ReadRect: exception while reading value '{0}': {1}", name, ex); - } - return defaultValue; - } - return defaultValue; - } - - public static Vector4 AsVector(this Rect rect) - { - return new Vector4(rect.x, rect.y, rect.width, rect.height); - } - } -} diff --git a/ReeperCommon/DraggableWindow.cs b/ReeperCommon/DraggableWindow.cs deleted file mode 100644 index 32e7056..0000000 --- a/ReeperCommon/DraggableWindow.cs +++ /dev/null @@ -1,315 +0,0 @@ -using UnityEngine; - -namespace ReeperCommon -{ - public delegate void WindowClosedDelegate(); - public delegate void WindowDelegate(bool tf); - - public abstract class DraggableWindow : MonoBehaviour - { - protected Rect windowRect = default(Rect); - protected Rect lastRect = default(Rect); - private GUISkin skin; - private int winId = Random.Range(2444, 2147483647); - private static Vector2 offset = new Vector2(4f, 4f); - private static GUIStyle buttonStyle; - private bool draggable = true; - private bool visible = true; - private static Texture2D hoverBackground; - private static GUISkin defaultSkin; - public event WindowDelegate OnVisibilityChange = delegate{}; - - public event WindowDelegate OnDraggabilityChange = delegate{}; - - public event WindowClosedDelegate OnClosed = delegate{}; - - public bool Draggable - { - get - { - return draggable; - } - protected set - { - if (draggable != value) - { - OnDraggabilityChange(value); - } - draggable = value; - } - } - - public bool ShrinkHeightToFit - { - get; - set; - } - - public bool Visible - { - get - { - return visible; - } - set - { - if (value != visible) - { - OnVisibilityChange(value); - } - visible = value; - if (gameObject.activeInHierarchy != visible && !visible) - { - OnClosed(); - } - gameObject.SetActive(visible); - } - } - - public int WindowID - { - get - { - return winId; - } - private set - { - winId = value; - } - } - - public string Title - { - get; - set; - } - - public GUISkin Skin - { - get - { - return skin ?? DefaultSkin; - } - set - { - skin = value ?? DefaultSkin; - } - } - - public Rect WindowRect - { - get - { - return lastRect; - } - } - - public bool ClampToScreen - { - get; - set; - } - - public static Texture2D LockTexture - { - get; - set; - } - - public static Texture2D UnlockTexture - { - get; - set; - } - - public static Texture2D CloseTexture - { - get; - set; - } - - public static Texture2D ButtonHoverBackground - { - get - { - return hoverBackground ?? ResourceUtil.GenerateRandom(16, 16); - } - set - { - hoverBackground = value; - if (buttonStyle != null) - { - buttonStyle.hover.background = value; - } - } - } - - public static string ButtonSound - { - get; - set; - } - - public static GUISkin DefaultSkin - { - get - { - return defaultSkin ?? HighLogic.Skin; - } - set - { - defaultSkin = value; - } - } - - protected void Awake() - { - if (buttonStyle == null) - { - buttonStyle = new GUIStyle(GUIStyle.none); - if (hoverBackground != null) - { - buttonStyle.hover.background = hoverBackground; - } - } - Draggable = true; - Visible = true; - ClampToScreen = true; - Title = "Draggable Window"; - windowRect = Setup(); - lastRect = new Rect(windowRect); - GameEvents.onHideUI.Add(OnHideUI); - GameEvents.onShowUI.Add(OnShowUI); - Log.Debug("ALERT:DraggableWindow {0} Awake", Title); - } - - private void Start() - { - Log.Debug("ALERT:DraggableWindow {0} Start", Title); - } - - protected virtual void OnDestroy() - { - Log.Debug("ALERT:DraggableWindow.OnDestroy"); - GameEvents.onHideUI.Remove(OnHideUI); - GameEvents.onShowUI.Remove(OnShowUI); - } - - protected void OnEnable() - { - OnVisibilityChange(true); - } - - protected void OnDisable() - { - OnVisibilityChange(false); - } - - public void Show(bool tf) - { - Visible = tf; - } - - protected void Update() - { - if (ShrinkHeightToFit) - { - windowRect.height = 1f; - } - } - - protected void OnGUI() - { - GUI.skin = Skin; - windowRect = GUILayout.Window(winId, windowRect, _InternalDraw, Title); - if (ClampToScreen) - windowRect = KSPUtil.ClampRectToScreen(windowRect); - } - - private void _InternalDraw(int winid) - { - DrawUI(); - lastRect.x = windowRect.x; - lastRect.y = windowRect.y; - GUILayout.BeginArea(new Rect(0f, offset.y, lastRect.width, lastRect.height)); - lastRect = new Rect(windowRect); - GUILayout.BeginHorizontal(GUILayout.ExpandWidth(true), GUILayout.ExpandHeight(false)); - GUILayout.FlexibleSpace(); - if (LockTexture != null && UnlockTexture != null) - { - if (GUILayout.Button(Draggable ? UnlockTexture : LockTexture, buttonStyle)) - { - Draggable = !Draggable; - if (!string.IsNullOrEmpty(ButtonSound)) - { - AudioPlayer.Audio.PlayUI(ButtonSound); - } - Log.Debug("ALERT:DraggableWindow {0}", Draggable ? "unlocked" : "locked"); - } - if (CloseTexture != null) - { - GUILayout.Space(offset.x * 0.5f); - } - } - if (CloseTexture != null && GUILayout.Button(CloseTexture, buttonStyle)) - { - if (!string.IsNullOrEmpty(ButtonSound)) - { - AudioPlayer.Audio.PlayUI(ButtonSound); - } - OnCloseClick(); - } - GUILayout.Space(offset.x); - GUILayout.EndHorizontal(); - GUILayout.EndArea(); - if (Draggable) - { - GUI.DragWindow(); - } - } - - protected abstract Rect Setup(); - - protected abstract void DrawUI(); - - protected abstract void OnCloseClick(); - - private void OnHideUI() - { - gameObject.SetActive(false); - } - - private void OnShowUI() - { - gameObject.SetActive(Visible); - } - - public void SaveInto(ConfigNode node) - { - if (node != null) - { - node.Set("WindowX", windowRect.x); - node.Set("WindowY", windowRect.y); - node.Set("Draggable", Draggable); - node.Set("Visible", Visible); - Log.Debug("ALERT:DraggableWindow.SaveInto: Saved window {0} as ConfigNode {1}", Title, node.ToString()); - return; - } - Log.Warning("GuiUtil.DraggableWindow: Can't save into null ConfigNode"); - } - - public bool LoadFrom(ConfigNode node) - { - if (node != null) - { - windowRect.x = node.Parse("WindowX", (float)Screen.width * 0.5f - windowRect.width * 0.5f); - windowRect.y = node.Parse("WindowY", (float)Screen.height * 0.5f - windowRect.height * 0.5f); - Draggable = node.Parse("Draggable", true); - Visible = node.Parse("Visible", false); - return node.HasValue("WindowX") && node.HasValue("WindowY"); - } - Log.Warning("GuiUtil.DraggableWindow: Can't load from null ConfigNode"); - return false; - } - } -} diff --git a/ReeperCommon/Log.cs b/ReeperCommon/Log.cs deleted file mode 100644 index 580032d..0000000 --- a/ReeperCommon/Log.cs +++ /dev/null @@ -1,141 +0,0 @@ -namespace ReeperCommon -{ - internal class Log - { - [System.Flags] - internal enum LogMask - { - Normal = 1, - Debug = 2, - Verbose = 4, - Performance = 8, - Warning = 16, - Error = 32, - None = 0, - All = -1 - } - - internal static LogMask Level = LogMask.Normal | LogMask.Warning | LogMask.Error; - - internal static string _AssemblyName => System.Reflection.Assembly.GetExecutingAssembly().GetName().Name; - - internal string _ClassName => GetType().Name; - - private static string FormatMessage(string msg) - { - return $"{_AssemblyName}, {msg}"; - } - - private static bool ShouldLog(LogMask messageType) - { - return (Level & messageType) != LogMask.None; - } - - internal static void Write(string message, LogMask level) - { - if (ShouldLog(level)) - { - string message2 = FormatMessage(message); - if ((level & LogMask.Error) != LogMask.None) - { - UnityEngine.Debug.LogError(message2); - return; - } - if ((level & LogMask.Warning) != LogMask.None) - { - UnityEngine.Debug.LogWarning(message2); - return; - } - if ((level & LogMask.Normal) != LogMask.None) - { - UnityEngine.Debug.Log(message2); - return; - } - if ((level & LogMask.Performance) != LogMask.None) - { - UnityEngine.Debug.Log(FormatMessage($"[PERF] {message}")); - return; - } - UnityEngine.Debug.Log(message2); - } - } - - internal static void Write(string message, LogMask level, params object[] strParams) - { - if (ShouldLog(level)) - { - Write(string.Format(message, strParams), level); - } - } - - internal static void SaveInto(ConfigNode parentNode) - { - ConfigNode configNode = parentNode.AddNode(new ConfigNode("LogSettings")); - configNode.AddValue("LogMask", (int)Level); - string[] names = System.Enum.GetNames(typeof(LogMask)); - System.Array values = System.Enum.GetValues(typeof(LogMask)); - configNode.AddValue("// Bit index", "message type"); - for (int i = 0; i < names.Length - 1; i++) - { - configNode.AddValue($"// Bit {i}", values.GetValue(i)); - } - Debug("[ScienceAlert].SaveInto = {0}", configNode.ToString()); - } - - internal static void LoadFrom(ConfigNode parentNode) - { - if (parentNode == null || !parentNode.HasNode("LogSettings")) - { - Warning("[ScienceAlert] failed, did not find LogSettings in: {0}", parentNode != null ? parentNode.ToString() : ""); - return; - } - ConfigNode node = parentNode.GetNode("LogSettings"); - try - { - if (!node.HasValue("LogMask")) - { - throw new System.Exception("[ScienceAlert]:No LogMask value in ConfigNode"); - } - string value = node.GetValue("LogMask"); - int num = 0; - if (int.TryParse(value, out num)) - { - if (num == 0) - { - Warning("[ScienceAlert]: Log disabled"); - } - Level = (LogMask)num; - Debug("[ScienceAlert]:Loaded LogMask = {0} from ConfigNode", Level.ToString()); - } - else - { - Debug("[ScienceAlert]: LogMask value '{0}' cannot be converted to LogMask", value); - } - } - catch (System.Exception ex) - { - Warning("[ScienceAlert] failed with exception: {0}", ex); - } - } - - internal static void Debug(string message, params object[] strParams) - { - Write(message, LogMask.Debug, strParams); - } - - internal static void Normal(string message, params object[] strParams) - { - Write(message, LogMask.Normal, strParams); - } - - internal static void Warning(string message, params object[] strParams) - { - Write(message, LogMask.Warning, strParams); - } - - internal static void Error(string message, params object[] strParams) - { - Write(message, LogMask.Error, strParams); - } - } -} diff --git a/ReeperCommon/PlayableSound.cs b/ReeperCommon/PlayableSound.cs deleted file mode 100644 index 5777dad..0000000 --- a/ReeperCommon/PlayableSound.cs +++ /dev/null @@ -1,33 +0,0 @@ -using UnityEngine; - -namespace ReeperCommon -{ - internal class PlayableSound - { - public AudioClip clip; - - public string shortName = ""; - - public float nextPlayableTime; - - internal PlayableSound(AudioClip aclip) - { - clip = aclip; - nextPlayableTime = 0f; - shortName = GetShortName(aclip.name); - } - - public static string GetShortName(string name) - { - if (name.Contains("/")) - { - int num = name.LastIndexOf('/'); - if (num >= 0) - { - return name.Substring(num + 1); - } - } - return name; - } - } -} diff --git a/ReeperCommon/ReeperConfigNodeExtensions.cs b/ReeperCommon/ReeperConfigNodeExtensions.cs deleted file mode 100644 index bc57bcf..0000000 --- a/ReeperCommon/ReeperConfigNodeExtensions.cs +++ /dev/null @@ -1,255 +0,0 @@ -using System; -using System.Linq; -using System.Reflection; -using System.Runtime.CompilerServices; - -namespace ReeperCommon -{ - [System.AttributeUsage(System.AttributeTargets.Property | System.AttributeTargets.Field)] - internal class Subsection : System.Attribute - { - private string sectionName = "Subsection"; - - public string Section => sectionName; - - public Subsection(string name) - { - sectionName = name; - if (string.IsNullOrEmpty(name)) - { - sectionName = "Subsection"; - } - } - } - - public static class ReeperConfigNodeExtensions - { - internal static ConfigNode CreateConfigFromObjectEx(this object obj, ConfigNodeTypeHandler typeFormatter = null) - { - ConfigNode result; - try - { - ConfigNode configNode = new ConfigNode(obj.GetType().Name); - typeFormatter = typeFormatter ?? new ConfigNodeTypeHandler(); - FieldInfo[] objectFields = GetObjectFields(obj); - FieldInfo[] array = objectFields; - for (int i = 0; i < array.Length; i++) - { - FieldInfo fieldInfo = array[i]; - object[] customAttributes = fieldInfo.GetCustomAttributes(false); - object value = fieldInfo.GetValue(obj); - if (value != null) - { - if (typeof(ConfigNode).IsAssignableFrom(fieldInfo.FieldType)) - { - ConfigNode configNode2 = new ConfigNode(fieldInfo.Name); - ConfigNode configNode3 = ((ConfigNode)Convert.ChangeType(value, typeof(ConfigNode))).CreateCopy(); - if (string.IsNullOrEmpty(configNode3.name)) - configNode3.name = "ConfigNode"; - configNode2.ClearData(); - Subsection subsection = customAttributes.SingleOrDefault(attr => attr is Subsection) as Subsection; - if (subsection == null) - configNode2.AddNode(configNode3); - else - configNode2.AddNode(subsection.Section).AddNode(configNode3); - configNode.AddNode(configNode2); - } - else - { - MethodInfo method = typeFormatter.GetType().GetMethod("Serialize", BindingFlags.Instance | BindingFlags.NonPublic); - if (method == null) - { - Log.Debug("[ScienceAlert]:CreateConfigFromObjectEx: Serialize method not found"); - } - MethodInfo methodInfo = method.MakeGenericMethod(fieldInfo.FieldType); - string value2 = methodInfo.Invoke(typeFormatter, new[] - { - value - }) as string; - if (string.IsNullOrEmpty(value2)) - { - Log.Warning("ConfigUtil.CreateConfigFromObjectEx: null or empty return value for serialized type {0}", fieldInfo.FieldType.Name); - } - WriteValue(configNode, fieldInfo.Name, value2, customAttributes); - } - } - else - { - Log.Warning("Could not get value for " + fieldInfo.Name); - } - } - PropertyInfo[] objectProperties = GetObjectProperties(obj); - PropertyInfo[] array2 = objectProperties; - for (int j = 0; j < array2.Length; j++) - { - PropertyInfo propertyInfo = array2[j]; - object obj2 = propertyInfo.GetGetMethod(true).Invoke(obj, null); - object[] customAttributes2 = propertyInfo.GetCustomAttributes(true); - MethodInfo method2 = typeFormatter.GetType().GetMethod("Serialize", BindingFlags.Instance | BindingFlags.NonPublic); - if (method2 == null) - { - Log.Debug("[ScienceAlert]:CreateConfigFromObjectEx: Serialize method not found"); - } - else - { - MethodInfo methodInfo2 = method2.MakeGenericMethod(propertyInfo.PropertyType); - string value3 = methodInfo2.Invoke(typeFormatter, new[] - { - obj2 - }) as string; - if (string.IsNullOrEmpty(value3)) - { - Log.Warning("ConfigUtil.CreateConfigFromObjectEx: null or empty return value for serialized type {0}", propertyInfo.PropertyType.Name); - } - WriteValue(configNode, propertyInfo.Name, value3, customAttributes2); - } - } - if (obj is IReeperSerializable) - { - ((IReeperSerializable)obj).OnSerialize(configNode); - } - result = configNode; - } - catch (Exception ex) - { - Log.Debug("[ScienceAlert]:ConfigUtil.CreateConfigFromObjectEx: Exception {0}", ex); - result = null; - } - return result; - } - - internal static bool CreateObjectFromConfigEx(this ConfigNode node, object obj, ConfigNodeTypeHandler typeFormatter = null) - { - bool flag = true; - typeFormatter = typeFormatter ?? new ConfigNodeTypeHandler(); - FieldInfo[] objectFields = GetObjectFields(obj); - PropertyInfo[] objectProperties = GetObjectProperties(obj); - Log.Debug("ALERT:CreateObjectFromConfig: Found {0} fields and {1} properties", objectFields.Length, objectProperties.Length); - FieldInfo[] array = objectFields; - for (int i = 0; i < array.Length; i++) - { - FieldInfo fieldInfo = array[i]; - try - { - object[] customAttributes = fieldInfo.GetCustomAttributes(true); - if (typeof(ConfigNode).IsAssignableFrom(fieldInfo.FieldType)) - { - if (node.HasNode(fieldInfo.Name)) - { - Convert.ChangeType(fieldInfo.GetValue(obj) ?? new ConfigNode(), typeof(ConfigNode)); - ConfigNode node2 = node.GetNode(fieldInfo.Name); - Subsection subsection = customAttributes.SingleOrDefault(attr => attr is Subsection) as Subsection; - if (subsection != null) - { - if (node2.HasNode(subsection.Section)) - { - node2 = node2.GetNode(subsection.Section); - } - } - if (node2.CountNodes == 1) - { - ConfigNode value = node2.nodes[0]; - fieldInfo.SetValue(obj, value); - } - } - } - else - { - string text = ReadValue(node, fieldInfo.Name, fieldInfo.GetCustomAttributes(true)); - if (!string.IsNullOrEmpty(text)) - { - MethodInfo method = typeFormatter.GetType().GetMethod("Deserialize", BindingFlags.Instance | BindingFlags.NonPublic); - MethodInfo methodInfo = method.MakeGenericMethod(fieldInfo.FieldType); - if (!(bool)methodInfo.Invoke(typeFormatter, new[] - { - fieldInfo.GetValue(obj), - text - })) - { - flag = false; - } - } - } - } - catch (Exception ex) - { - Log.Debug("[ScienceAlert]:Exception while deserializing field '{0}': {1}", fieldInfo.Name, ex); - flag = false; - } - } - PropertyInfo[] array2 = objectProperties; - for (int j = 0; j < array2.Length; j++) - { - PropertyInfo propertyInfo = array2[j]; - try - { - string text2 = ReadValue(node, propertyInfo.Name, propertyInfo.GetCustomAttributes(true)); - if (!string.IsNullOrEmpty(text2)) - { - MethodInfo method2 = typeFormatter.GetType().GetMethod("Deserialize", BindingFlags.Instance | BindingFlags.NonPublic); - MethodInfo methodInfo2 = method2.MakeGenericMethod(propertyInfo.PropertyType); - object obj2 = Convert.ChangeType(propertyInfo.GetGetMethod(true).Invoke(obj, null), propertyInfo.PropertyType); - object[] array3 = { obj2, text2 }; - if (!(bool)methodInfo2.Invoke(typeFormatter, array3)) - flag = false; - else - propertyInfo.SetValue(obj, array3[0], BindingFlags.Instance | BindingFlags.SetProperty, null, null, null); - } - } - catch (Exception ex2) - { - Log.Debug("[ScienceAlert]:Exception while deserializing property '{0}': {1}", propertyInfo.Name, ex2); - flag = false; - } - } - if (obj is IReeperSerializable) - ((IReeperSerializable)obj).OnDeserialize(node); - return flag && objectFields.Count() > 0 || obj is IReeperSerializable; - } - - private static FieldInfo[] GetObjectFields(object obj) - { - return (from fi in obj.GetType().GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy) - where !fi.GetCustomAttributes(false).Any(attr => attr is CompilerGeneratedAttribute || attr is NonSerializedAttribute || attr is DoNotSerialize) - select fi).ToArray(); - } - - private static PropertyInfo[] GetObjectProperties(object obj) - { - return obj.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy).Where(delegate(PropertyInfo pi) - { - if (pi.GetGetMethod(true) != null && pi.GetSetMethod(true) != null) - return !pi.GetCustomAttributes(true).Any(attr => attr is DoNotSerialize || attr is NonSerializedAttribute); - return false; - }).ToArray(); - } - - private static void WriteValue(ConfigNode node, string valueName, string value, object[] attrs) - { - if (attrs == null) attrs = new object[0]; - Subsection subsection = attrs.SingleOrDefault(attr => attr is Subsection) as Subsection; - if (subsection != null) - { - if (node.HasNode(subsection.Section)) - node = node.GetNode(subsection.Section); - else - node = node.AddNode(subsection.Section); - } - attrs.ToList().ForEach(delegate{}); - node.AddValue(valueName, value); - } - - private static string ReadValue(ConfigNode node, string valueName, object[] attrs) - { - if (attrs == null) - attrs = new object[0]; - Subsection subsection = attrs.SingleOrDefault(attr => attr is Subsection) as Subsection; - if (subsection != null) - { - if (node.HasNode(subsection.Section)) - node = node.GetNode(subsection.Section); - } - return node.ReadString(valueName); - } - } -} diff --git a/ReeperCommon/ResourceUtil.cs b/ReeperCommon/ResourceUtil.cs deleted file mode 100644 index 7069f78..0000000 --- a/ReeperCommon/ResourceUtil.cs +++ /dev/null @@ -1,259 +0,0 @@ -using System.IO; -using UnityEngine; - -namespace ReeperCommon -{ - public static class ResourceUtil - { - public static bool SaveToDisk(this Texture2D texture, string pathInGameData) - { - System.Collections.Generic.List list = new System.Collections.Generic.List - { - TextureFormat.Alpha8, - TextureFormat.RGB24, - TextureFormat.RGBA32, - TextureFormat.ARGB32 - }; - if (!list.Contains(texture.format)) - { - return texture.CreateReadable().SaveToDisk(pathInGameData); - } - if (pathInGameData.StartsWith("/")) - { - pathInGameData = pathInGameData.Substring(1); - } - pathInGameData = "/GameData/" + pathInGameData; - if (!pathInGameData.EndsWith(".png")) - { - pathInGameData += ".png"; - } - bool result; - try - { - FileStream output = new FileStream(KSPUtil.ApplicationRootPath + pathInGameData, FileMode.OpenOrCreate, FileAccess.Write); - BinaryWriter binaryWriter = new BinaryWriter(output); - binaryWriter.Write(texture.EncodeToPNG()); - result = true; - } - catch (System.Exception) - { - result = false; - } - return result; - } - - public static Texture2D as2D(this Texture tex) - { - return tex as Texture2D; - } - - public static Texture2D GetEmbeddedTexture(string resource, bool compress = false, bool mip = false) - { - Stream embeddedContentsStream = GetEmbeddedContentsStream(resource); - if (embeddedContentsStream == null) - { - Log.Debug("[ScienceAlert]:Failed to locate embedded texture '{0}'", resource); - return null; - } - byte[] array = new byte[16384]; - MemoryStream memoryStream = new MemoryStream(); - int count; - while ((count = embeddedContentsStream.Read(array, 0, array.Length)) > 0) - { - memoryStream.Write(array, 0, count); - } - Texture2D texture2D = new Texture2D(1, 1, compress ? TextureFormat.DXT5 : TextureFormat.ARGB32, mip); - if (texture2D.LoadImage(memoryStream.ToArray())) - { - return texture2D; - } - return null; - } - - public static bool GetEmbeddedContents(string resource, System.Reflection.Assembly assembly, out string contents) - { - contents = string.Empty; - try - { - Stream embeddedContentsStream = GetEmbeddedContentsStream(resource, assembly); - if (embeddedContentsStream != null) - { - StreamReader streamReader = new StreamReader(embeddedContentsStream); - if (streamReader != null) - { - contents = streamReader.ReadToEnd(); - return contents.Length > 0; - } - } - } - catch (System.Exception ex) - { - Log.Debug("[ScienceAlert]:GetEmbeddedContents: {0}", ex); - } - return false; - } - - public static bool GetEmbeddedContents(string resource, out string contents) - { - return GetEmbeddedContents(resource, System.Reflection.Assembly.GetExecutingAssembly(), out contents); - } - - public static byte[] GetEmbeddedContentsBytes(string resource, System.Reflection.Assembly assembly) - { - Stream embeddedContentsStream = GetEmbeddedContentsStream(resource, assembly); - if (embeddedContentsStream != null && embeddedContentsStream.Length > 0L) - { - byte[] array = new byte[embeddedContentsStream.Length]; - MemoryStream memoryStream = new MemoryStream(); - int count; - while ((count = embeddedContentsStream.Read(array, 0, array.Length)) > 0) - { - memoryStream.Write(array, 0, count); - } - return array; - } - return null; - } - - public static Stream GetEmbeddedContentsStream(string resource, System.Reflection.Assembly assembly) - { - return assembly.GetManifestResourceStream(resource); - } - - public static Stream GetEmbeddedContentsStream(string resource) - { - return GetEmbeddedContentsStream(resource, System.Reflection.Assembly.GetExecutingAssembly()); - } - - public static Texture2D LocateTexture(string textureName, bool relativeToGameData = false) - { - if (string.IsNullOrEmpty(textureName)) - { - return null; - } - byte[] embeddedContentsBytes = GetEmbeddedContentsBytes(textureName, System.Reflection.Assembly.GetExecutingAssembly()); - Texture2D texture2D; - if (embeddedContentsBytes != null) - { - texture2D = new Texture2D(1, 1, TextureFormat.ARGB32, false); - if (texture2D.LoadImage(embeddedContentsBytes)) - { - return texture2D; - } - } - string text = Path.GetFileNameWithoutExtension(textureName); - string text2 = Path.GetDirectoryName(textureName); - if (text.StartsWith("/") || text.StartsWith("\\")) - { - text = text.Substring(1); - } - if (text2.EndsWith("/") || text2.EndsWith("\\")) - { - text2 = text2.Substring(1); - } - if (relativeToGameData) - { - textureName = text2 + "/" + text; - } - else - { - textureName = ConfigUtil.GetRelativeToGameData(ConfigUtil.GetDllDirectoryPath()) + text2 + "/" + text; - } - texture2D = GameDatabase.Instance.GetTexture(textureName, false); - if (texture2D == null) - { - Log.Debug("[ScienceAlert]:Failed to find texture '{0}'", textureName); - } - return texture2D; - } - - public static void FlipTexture(Texture2D tex, bool horizontal, bool vertical) - { - Color32[] pixels = tex.GetPixels32(); - Color32[] array = new Color32[pixels.Length]; - for (int i = 0; i < tex.height; i++) - { - for (int j = 0; j < tex.width; j++) - { - int num = (vertical ? tex.height - i - 1 : i) * tex.width + (horizontal ? tex.width - j - 1 : j); - array[i * tex.width + j] = pixels[num]; - } - } - tex.SetPixels32(array); - tex.Apply(); - } - - public static Texture2D CreateReadable(this Texture2D original) - { - if (original.width == 0 || original.height == 0) - { - throw new System.Exception("CreateReadable: Original has zero width or height or both"); - } - Texture2D texture2D = new Texture2D(original.width, original.height); - RenderTexture temporary = RenderTexture.GetTemporary(original.width, original.height, 0, RenderTextureFormat.ARGB32, RenderTextureReadWrite.sRGB, 1); - Graphics.Blit(original, temporary); - RenderTexture.active = temporary; - texture2D.ReadPixels(new Rect(0f, 0f, (float)texture2D.width, (float)texture2D.height), 0, 0); - RenderTexture.active = null; - RenderTexture.ReleaseTemporary(temporary); - return texture2D; - } - - public static Texture2D Cutout(this Texture2D source, Rect src, bool rectIsInUV = false) - { - Rect src2 = new Rect(src); - if (rectIsInUV) - { - src2.x *= (float)source.width; - src2.width *= (float)source.width; - src2.y *= (float)source.height; - src2.height *= (float)source.height; - } - return Cutout_Internal(source, src2); - } - - public static Texture2D Cutout(this Renderer renderer, Rect uv) - { - return ((Texture2D)renderer.sharedMaterial.mainTexture).Cutout(uv, true); - } - - private static Texture2D Cutout_Internal(Texture2D source, Rect src, bool secondAttempt = false) - { - Texture2D texture2D = new Texture2D(Mathf.FloorToInt(src.width), Mathf.FloorToInt(src.height), TextureFormat.ARGB32, false); - Texture2D result; - try - { - Color[] pixels = source.GetPixels(Mathf.FloorToInt(src.x), Mathf.FloorToInt(src.y), Mathf.FloorToInt(src.width), Mathf.FloorToInt(src.height)); - texture2D.SetPixels(pixels); - texture2D.Apply(); - result = texture2D; - } - catch (System.Exception) - { - result = secondAttempt ? null : Cutout_Internal(source.CreateReadable(), src, true); - } - return result; - } - - public static void GenerateRandom(this Texture2D tex) - { - Color32[] pixels = tex.GetPixels32(); - for (int i = 0; i < tex.height; i++) - { - for (int j = 0; j < tex.width; j++) - { - pixels[i * tex.width + j] = new Color(Random.Range(0f, 1f), Random.Range(0f, 1f), Random.Range(0f, 1f), Random.Range(0f, 1f)); - } - } - tex.SetPixels32(pixels); - tex.Apply(); - } - - public static Texture2D GenerateRandom(int w, int h) - { - Texture2D texture2D = new Texture2D(w, h, TextureFormat.ARGB32, false); - texture2D.GenerateRandom(); - return texture2D; - } - } -} diff --git a/ScienceAlert.Experiments/BiomeFilter.cs b/ScienceAlert.Experiments/BiomeFilter.cs deleted file mode 100644 index 3926925..0000000 --- a/ScienceAlert.Experiments/BiomeFilter.cs +++ /dev/null @@ -1,186 +0,0 @@ -using System; -using ReeperCommon; -using UnityEngine; - -namespace ScienceAlert.Experiments -{ - public class BiomeFilter : MonoBehaviour - { - private const int HALF_SEARCH_DIMENSIONS = 2; // box around the point on the biome map to - - private CelestialBody current; // which CelestialBody we've got a cached biome map texture for - private Texture2D projectedMap; // this is the cleaned biome map of the current CelestialBody - private System.Collections.IEnumerator projector; // this coroutine constructs the projectedMap from current CelestialBody - private const float COLOR_THRESHOLD = 0.005f; // Maximum color difference for two colors to be considered the same - - void Start() - { - GameEvents.onDominantBodyChange.Add(OnDominantBodyChanged); - GameEvents.onVesselChange.Add(OnVesselChanged); - ReprojectBiomeMap(FlightGlobals.currentMainBody); - } - - void OnDestroy() - { - GameEvents.onVesselChange.Remove(OnVesselChanged); - GameEvents.onDominantBodyChange.Remove(OnDominantBodyChanged); - } - - public void Update() - { - if (projector != null) - projector.MoveNext(); - } - - public bool GetCurrentBiome(out string biome) - { - biome = "N/A"; - - if (FlightGlobals.ActiveVessel == null) return false; - - string possibleBiome = string.Empty; - - if (GetBiome(FlightGlobals.ActiveVessel.latitude * Mathf.Deg2Rad, FlightGlobals.ActiveVessel.longitude * Mathf.Deg2Rad, out possibleBiome)) - { - // the biome we got is most likely good - biome = possibleBiome; - return true; - } - // the biome we got is not very accurate (e.g. polar ice caps in middle of kerbin grasslands and - // such, due to the way the biome map is filtered). - biome = possibleBiome; - return false; - } - - public bool GetBiome(double latRad, double lonRad, out string biome) - { - biome = string.Empty; - var vessel = FlightGlobals.ActiveVessel; - - if (vessel == null || vessel.mainBody.BiomeMap == null || vessel.mainBody.BiomeMap.MapName == null) - return true; - - if (!string.IsNullOrEmpty(vessel.landedAt)) - { - biome = Vessel.GetLandedAtString(vessel.landedAt); - return true; - } - - var possibleBiome = vessel.mainBody.BiomeMap.GetAtt(latRad, lonRad); - - if (!IsBusy) - { - if (!VerifyBiomeResult(latRad, lonRad, possibleBiome)) return false; - biome = possibleBiome.name; - return true; - } - - biome = possibleBiome.name; - return true; - } - - private bool Similar(Color first, Color second) - { - return Mathf.Abs(first.r - second.r) < COLOR_THRESHOLD && Mathf.Abs(first.g - second.g) < COLOR_THRESHOLD && Mathf.Abs(first.b - second.b) < COLOR_THRESHOLD; - } - - private bool VerifyBiomeResult(double lat, double lon, CBAttributeMapSO.MapAttribute target) - { - if (projectedMap == null) return true; // we'll have to assume it's accurate since we can't prove otherwise - if (target == null || target.mapColor == null) return true; // this shouldn't happen - - lon -= Mathf.PI * 0.5f; - if (lon < 0d) lon += Mathf.PI * 2d; - lon %= Mathf.PI * 2d; - - int x_center = (int)Math.Round(projectedMap.width * (float)(lon / (Mathf.PI * 2)), 0); - int y_center = (int)Math.Round(projectedMap.height * ((float)(lat / Mathf.PI) + 0.5f), 0); - - for (int y = y_center - HALF_SEARCH_DIMENSIONS; y < y_center + HALF_SEARCH_DIMENSIONS; ++y) - for (int x = x_center - HALF_SEARCH_DIMENSIONS; x < x_center + HALF_SEARCH_DIMENSIONS; ++x) - { - Color c = projectedMap.GetPixel(x, y); - if (Similar(c, target.mapColor)) - return true; // we have a match, no need to look further - } - return false; - } - - private void ReprojectBiomeMap(CelestialBody newBody) - { - projector = ReprojectMap(newBody); - } - - private System.Collections.IEnumerator ReprojectMap(CelestialBody newBody) - { - if (current == newBody) - { - projector = null; - yield break; - } - - if (newBody == null) - { - projector = null; - current = null; - yield break; - } - - current = null; - - if (newBody.BiomeMap == null || newBody.BiomeMap.MapName == null) - { - projectedMap = null; - projector = null; - yield break; - } - - Texture2D projection = new Texture2D(newBody.BiomeMap.Width, newBody.BiomeMap.Height, TextureFormat.ARGB32, false); - projection.filterMode = FilterMode.Point; - - yield return null; - - float timer = Time.realtimeSinceStartup; - Color32[] pixels = projection.GetPixels32(); - - for (int y = 0; y < projection.height; ++y) - { - for (int x = 0; x < projection.width; ++x) - { - // convert x and y into uv coordinates - float u = (float)x / projection.width; - float v = (float)y / projection.height; - - // convert uv coordinates into latitude and longitude - double lat = Math.PI * v - Math.PI * 0.5; - double lon = 2d * Math.PI * u + Math.PI * 0.5; - - // set biome color in our clean texture - pixels[y * projection.width + x] = (Color32)newBody.BiomeMap.GetAtt(lat, lon).mapColor; - } - - if (y % 5 == 0) - yield return null; - } - - projection.SetPixels32(pixels); - projection.Apply(); - - current = newBody; - projectedMap = projection; - projector = null; // we're finished! - } - - private void OnDominantBodyChanged(GameEvents.FromToAction bodies) - { - ReprojectBiomeMap(bodies.to); - } - - private void OnVesselChanged(Vessel v) - { - ReprojectBiomeMap(v.mainBody); - } - - public bool IsBusy => projector != null; - } -} \ No newline at end of file diff --git a/ScienceAlert.ProfileData/ExperimentSettings.cs b/ScienceAlert.ProfileData/ExperimentSettings.cs deleted file mode 100644 index ef75680..0000000 --- a/ScienceAlert.ProfileData/ExperimentSettings.cs +++ /dev/null @@ -1,148 +0,0 @@ -using ReeperCommon; - -namespace ScienceAlert.ProfileData -{ - public class ExperimentSettings - { - public enum FilterMethod - { - Unresearched, - NotMaxed, - LessThanFiftyPercent, - LessThanNinetyPercent - } - - private bool _enabled = true; - private bool _soundOnDiscovery = true; - private bool _animationOnDiscovery = true; - private bool _stopWarpOnDiscovery; - private FilterMethod _filter; - public bool IsDefault; - public event Callback OnChanged = delegate{}; - - public bool Enabled - { - get - { - return _enabled; - } - set - { - if (value == _enabled) return; - _enabled = value; - OnChanged(); - } - } - - public bool SoundOnDiscovery - { - get - { - return _soundOnDiscovery; - } - set - { - if (_soundOnDiscovery != value) - { - _soundOnDiscovery = value; - OnChanged(); - } - } - } - - public bool AnimationOnDiscovery - { - get - { - return _animationOnDiscovery; - } - set - { - if (value != _animationOnDiscovery) - { - _animationOnDiscovery = value; - OnChanged(); - } - } - } - - public bool StopWarpOnDiscovery - { - get - { - return _stopWarpOnDiscovery; - } - set - { - if (value != _stopWarpOnDiscovery) - { - _stopWarpOnDiscovery = value; - OnChanged(); - } - } - } - - public FilterMethod Filter - { - get - { - return _filter; - } - set - { - if (value != _filter) - { - _filter = value; - OnChanged(); - } - } - } - - public ExperimentSettings() - { - } - - public ExperimentSettings(ExperimentSettings other) - { - Enabled = other.Enabled; - SoundOnDiscovery = other.SoundOnDiscovery; - AnimationOnDiscovery = other.AnimationOnDiscovery; - StopWarpOnDiscovery = other.StopWarpOnDiscovery; - Filter = other.Filter; - IsDefault = other.IsDefault; - } - - public void OnLoad(ConfigNode node) - { - Enabled = node.Parse("Enabled", true); - SoundOnDiscovery = node.Parse("SoundOnDiscovery", true); - AnimationOnDiscovery = node.Parse("AnimationOnDiscovery", true); - StopWarpOnDiscovery = node.Parse("StopWarpOnDiscovery", false); - string value = node.GetValue("Filter"); - if (string.IsNullOrEmpty(value)) - { - Log.Debug("[ScienceAlert]:Settings: invalid experiment filter"); - value = System.Enum.GetValues(typeof(FilterMethod)).GetValue(0).ToString(); - } - Filter = (FilterMethod)System.Enum.Parse(typeof(FilterMethod), value); - IsDefault = node.Parse("IsDefault", false); - } - - public void OnSave(ConfigNode node) - { - node.AddValue("Enabled", Enabled); - node.AddValue("SoundOnDiscovery", SoundOnDiscovery); - node.AddValue("AnimationOnDiscovery", AnimationOnDiscovery); - node.AddValue("StopWarpOnDiscovery", StopWarpOnDiscovery); - node.AddValue("Filter", Filter); - node.AddValue("IsDefault", IsDefault); - } - - public override string ToString() - { - ConfigNode configNode = new ConfigNode(); - OnSave(configNode); - return configNode.ToString(); - } - } -} diff --git a/ScienceAlert.ProfileData/Profile.cs b/ScienceAlert.ProfileData/Profile.cs deleted file mode 100644 index dafcee0..0000000 --- a/ScienceAlert.ProfileData/Profile.cs +++ /dev/null @@ -1,170 +0,0 @@ -using System.Collections.Generic; -using ReeperCommon; - -namespace ScienceAlert.ProfileData -{ - internal class Profile - { - [Persistent(isPersistant = true)] - public string name = string.Empty; - - [Persistent] - public bool modified; - - [Persistent] - public float scienceThreshold; - - [System.NonSerialized] - public Dictionary settings; - - public ExperimentSettings this[string expid] - { - get - { - if (settings.ContainsKey(expid)) - { - return settings[expid]; - } - settings[expid] = new ExperimentSettings(); - return settings[expid]; - } - private set - { - settings.Add(expid.ToLower(), value); - } - } - - public string DisplayName - { - get - { - if (modified) - { - return "*" + name + "*"; - } - return name; - } - } - - public float ScienceThreshold - { - get - { - return scienceThreshold; - } - set - { - if (value != scienceThreshold) - { - modified = true; - } - scienceThreshold = value; - } - } - - public Profile(ConfigNode node) - { - Setup(); - OnLoad(node); - RegisterEvents(); - } - - public Profile(string name) - { - Log.Debug("VERB ALERT:Creating profile '{0}' with default values", name); - this.name = name; - Setup(); - RegisterEvents(); - } - - public Profile(Profile other) - { - Dictionary.KeyCollection keys = other.settings.Keys; - settings = new Dictionary(); - foreach (string current in keys) - { - settings.Add(current, new ExperimentSettings(other.settings[current])); - } - name = string.Copy(other.name); - modified = other.modified; - scienceThreshold = other.scienceThreshold; - RegisterEvents(); - } - - private void Setup() - { - settings = new Dictionary(); - try - { - List experimentIDs = ResearchAndDevelopment.GetExperimentIDs(); - foreach (string current in experimentIDs) - { - settings.Add(current, new ExperimentSettings()); - } - } - catch (System.Exception ex) - { - Log.Debug("[ScienceAlert]:Profile '{1}' constructor exception: {0}", ex, string.IsNullOrEmpty(name) ? "(unnamed)" : name); - } - } - - public void OnSave(ConfigNode node) - { - ConfigNode.CreateConfigFromObject(this, 0, node); - foreach (KeyValuePair current in settings) - { - current.Value.OnSave(node.AddNode(new ConfigNode(current.Key))); - } - Log.Debug("ALERT:Profile: OnSave config: {0}", node.ToString()); - } - - public void OnLoad(ConfigNode node) - { - Log.Debug("ALERT:Loading profile..."); - ConfigNode.LoadObjectFromConfig(this, node); - if (string.IsNullOrEmpty(name)) - { - name = "nameless." + System.Guid.NewGuid(); - } - else - { - Log.Debug("ALERT:Profile name is '{0}'", name); - } - string[] array = node.nodes.DistinctNames(); - for (int i = 0; i < array.Length; i++) - { - string text = array[i]; - ConfigNode node2 = node.GetNode(text); - if (!settings.ContainsKey(text)) - { - settings.Add(text, new ExperimentSettings()); - } - settings[text].OnLoad(node2); - } - } - - public Profile Clone() - { - return new Profile(this); - } - - public static Profile MakeDefault() - { - return new Profile("default"); - } - - private void SettingChanged() - { - Log.Debug("ALERT:Profile '{0}' was modified!", name); - modified = true; - } - - private void RegisterEvents() - { - foreach (KeyValuePair current in settings) - { - current.Value.OnChanged += SettingChanged; - } - } - } -} diff --git a/ScienceAlert.Toolbar/BlizzyInterface.cs b/ScienceAlert.Toolbar/BlizzyInterface.cs deleted file mode 100644 index d4a4392..0000000 --- a/ScienceAlert.Toolbar/BlizzyInterface.cs +++ /dev/null @@ -1,392 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Linq; -using UnityEngine; -using ReeperCommon; - -namespace ScienceAlert.Toolbar -{ - class BlizzyInterface : MonoBehaviour, IToolbar - { - private const string NormalFlaskTexture = "ScienceAlert/textures/flask"; - private List StarFlaskTextures = new List(); - private float FrameRate = 24f; - private int FrameCount = 100; - private int CurrentFrame = 0; - - IButton button; - IEnumerator animation; - - public event ToolbarClickHandler OnClick; - - void Start() - { - SliceAtlasTexture(); - button = ToolbarManager.Instance.add("ScienceAlert", "PopupOpen"); - button.Text = "Science Alert"; - button.ToolTip = "Left-click to view alert experiments; Right-click for settings"; - button.TexturePath = NormalFlaskTexture; - button.OnClick += ce => { - OnClick(new ClickInfo { button = ce.MouseButton, used = false }); - }; - FrameRate = Settings.Instance.StarFlaskFrameRate; - } - - private void SliceAtlasTexture() - { - Func GetFrame = delegate (int frame, int desiredLen) - { - string str = frame.ToString(); - while (str.Length < desiredLen) - str = "0" + str; - return str; - }; - - // load textures - try - { - if (!GameDatabase.Instance.ExistsTexture(NormalFlaskTexture)) - { - // load normal flask texture - Log.Debug("Loading normal flask texture"); - - Texture2D nflask = ResourceUtil.GetEmbeddedTexture("Textures.flask.png", true); - if (nflask == null) - { - Log.Error("Failed to create normal flask texture!"); - } - else - { - GameDatabase.TextureInfo ti = new GameDatabase.TextureInfo(null, nflask, false, true, true); - ti.name = NormalFlaskTexture; - GameDatabase.Instance.databaseTexture.Add(ti); - Log.Debug("Created normal flask texture {0}", ti.name); - } - - Texture2D sheet = ResourceUtil.GetEmbeddedTexture("Textures.sheet.png"); - if (sheet == null) - { - Log.Error("Failed to create sprite sheet texture!"); - } - else - { - var rt = RenderTexture.GetTemporary(sheet.width, sheet.height); - var oldRt = RenderTexture.active; - int invertHeight = ((FrameCount - 1) / (sheet.width / 24)) * 24; - - Graphics.Blit(sheet, rt); - RenderTexture.active = rt; - - for (int i = 0; i < FrameCount; ++i) - { - StarFlaskTextures.Add(NormalFlaskTexture + GetFrame(i + 1, 4)); - Texture2D sliced = new Texture2D(24, 24, TextureFormat.ARGB32, false); - - sliced.ReadPixels(new Rect((i % (sheet.width / 24)) * 24, /*invertHeight -*/ (i / (sheet.width / 24)) * 24, 24, 24), 0, 0); - sliced.Apply(); - - GameDatabase.TextureInfo ti = new GameDatabase.TextureInfo(null, sliced, false, false, false); - ti.name = StarFlaskTextures.Last(); - - GameDatabase.Instance.databaseTexture.Add(ti); - Log.Debug("Added sheet texture {0}", ti.name); - } - - RenderTexture.active = oldRt; - RenderTexture.ReleaseTemporary(rt); - } - Log.Debug("Finished loading sprite sheet textures."); - } - else - { // textures already loaded - for (int i = 0; i < FrameCount; ++i) - StarFlaskTextures.Add(NormalFlaskTexture + GetFrame(i + 1, 4)); - } - } - catch (Exception e) - { - Log.Error("Failed to load textures: {0}", e); - } - } - - /// - /// Normal cleanup - /// - void OnDestroy() - { - //Log.Verbose("Destroying BlizzyInterface"); - button.Destroy(); - } - - /// - /// Begins playing the "star flask" animation, used when a new - /// experiment has become available. - /// - public void PlayAnimation() - { - if (animation == null) animation = DoAnimation(); - } - - /// - /// Stops playing animation (but leaves the current frame state) - /// - public void StopAnimation() - { - animation = null; - } - - /// - /// Switch to normal flask texture - /// - public void SetUnlit() - { - animation = null; - button.TexturePath = NormalFlaskTexture; - } - - public void SetLit() - { - animation = null; - button.TexturePath = StarFlaskTextures[0]; - } - - public IDrawable Drawable - { - get - { - return button.Drawable; - } - - set - { - button.Drawable = value; - } - } - - public bool Important - { - get - { - return button.Important; - } - - set - { - button.Important = value; - } - } - - public bool IsAnimating => animation != null; - - public bool IsLit => animation == null && button.TexturePath != NormalFlaskTexture; - - public bool IsNormal => !IsAnimating && !IsLit; - - void Update() - { - if (animation != null) animation.MoveNext(); - } - - /// - /// Is called by Update whenever animation exists to - /// update animation frame. - /// - /// Note: I didn't make this into an actual coroutine - /// because StopCoroutine seems to sometimes throw - /// exceptions - /// - /// - IEnumerator DoAnimation() - { - float elapsed = 0f; - while (true) - { - while (elapsed < 1f / FrameRate) - { - elapsed += Time.deltaTime; - yield return new WaitForSeconds(1f / FrameRate); - } - elapsed -= 1f / FrameRate; - CurrentFrame = (CurrentFrame + 1) % FrameCount; - button.TexturePath = StarFlaskTextures[CurrentFrame]; - } - } - } -} - - - -//using System; -//using System.Collections; -//using System.Collections.Generic; -//using System.Linq; -//using ReeperCommon; -//using UnityEngine; - -//namespace ScienceAlert.Toolbar -//{ -// internal class BlizzyInterface : MonoBehaviour, IToolbar -// { -// private readonly List StarFlaskTextures = new List(); -// private float FrameRate = 24f; -// private int FrameCount = 100; -// private int CurrentFrame; -// private IButton button; -// private IEnumerator animation; -// public event ToolbarClickHandler OnClick; - -// public IDrawable Drawable -// { -// get -// { -// return button.Drawable; -// } -// set -// { -// button.Drawable = value; -// } -// } - -// public bool Important -// { -// get -// { -// return button.Important; -// } -// set -// { -// button.Important = value; -// } -// } - -// public bool IsAnimating => animation != null; - -// public bool IsLit => animation == null && button.TexturePath != "ScienceAlert/textures/flask"; - -// public bool IsNormal => !IsAnimating && !IsLit; - -// private void Start() -// { -// SliceAtlasTexture(); -// button = ToolbarManager.Instance.add("ScienceAlert", "PopupOpen"); -// button.Text = "Science Alert"; -// button.ToolTip = "Left-click to view alert experiments; Right-click for settings"; -// button.OnClick += delegate(ClickEvent ce) -// { -// OnClick(new ClickInfo{button = ce.MouseButton,used = false}); -// }; -// FrameRate = Settings.Instance.StarFlaskFrameRate; -// } - -// private void SliceAtlasTexture() -// { -// Func func = delegate(int frame, int desiredLen) -// { -// string text = frame.ToString(); -// while (text.Length < desiredLen) -// { -// text = "0" + text; -// } -// return text; -// }; -// try -// { -// if (!GameDatabase.Instance.ExistsTexture("ScienceAlert/textures/flask")) -// { -// Texture2D embeddedTexture = ResourceUtil.GetEmbeddedTexture("Textures.flask.png", true); -// GameDatabase.TextureInfo textureInfo = new GameDatabase.TextureInfo(null, embeddedTexture, false, true, true); -// GameDatabase.Instance.databaseTexture.Add(textureInfo); -// Texture2D embeddedTexture2 = ResourceUtil.GetEmbeddedTexture("Textures.sheet.png"); -// RenderTexture temporary = RenderTexture.GetTemporary(embeddedTexture2.width, embeddedTexture2.height); -// RenderTexture active = RenderTexture.active; -// Graphics.Blit(embeddedTexture2, temporary); -// RenderTexture.active = temporary; -// for (var i = 0; i < FrameCount; i++) -// { -// StarFlaskTextures.Add("ScienceAlert/textures/flask" + func(i + 1, 4)); -// Texture2D texture2D = new Texture2D(24, 24, TextureFormat.ARGB32, false); -// texture2D.ReadPixels(new Rect(i % (embeddedTexture2.width / 24) * 24, i / (embeddedTexture2.width / 24) * 24, 24f, 24f), 0, 0); -// texture2D.Apply(); -// GameDatabase.TextureInfo textureInfo2 = -// new GameDatabase.TextureInfo(null, texture2D, false, false, false) -// { -// name = StarFlaskTextures.Last() -// }; -// GameDatabase.Instance.databaseTexture.Add(textureInfo2); -// } -// RenderTexture.active = active; -// RenderTexture.ReleaseTemporary(temporary); -// } -// else -// { -// for (int j = 0; j < FrameCount; j++) -// { -// StarFlaskTextures.Add("ScienceAlert/textures/flask" + func(j + 1, 4)); -// } -// } -// } -// catch (Exception ex) -// { -// Log.Debug("[ScienceAlert]:Failed to load textures: {0}", ex); -// } -// } - -// private void OnDestroy() -// { -// Log.Debug("VERB ALERT:Destroying BlizzyInterface"); -// button.Destroy(); -// } - -// public void PlayAnimation() -// { -// if (animation == null) -// { -// animation = DoAnimation(); -// } -// } - -// public void StopAnimation() -// { -// animation = null; -// } - -// public void SetUnlit() -// { -// animation = null; -// } - -// public void SetLit() -// { -// animation = null; -// button.TexturePath = StarFlaskTextures[0]; -// } - -// private void Update() -// { -// if (animation != null) -// { -// animation.MoveNext(); -// } -// } - -// private IEnumerator DoAnimation() -// { -// float num = 0f; -// while (true) -// { -// if (num >= 1f / FrameRate) -// { -// num -= 1f / FrameRate; -// CurrentFrame = (CurrentFrame + 1) % FrameCount; -// button.TexturePath = StarFlaskTextures[CurrentFrame]; -// } -// else -// { -// num += Time.deltaTime; -// yield return new WaitForSeconds(1f / FrameRate); -// } -// } -// } -// } -//} diff --git a/ScienceAlert.Toolbar/ClickInfo.cs b/ScienceAlert.Toolbar/ClickInfo.cs deleted file mode 100644 index da46763..0000000 --- a/ScienceAlert.Toolbar/ClickInfo.cs +++ /dev/null @@ -1,21 +0,0 @@ -namespace ScienceAlert.Toolbar -{ - public class ClickInfo - { - public int button; - public bool used; - - public bool Unused => !used; - - public ClickInfo() - { - button = 0; - used = false; - } - - public void Consume() - { - used = true; - } - } -} diff --git a/ScienceAlert.Toolbar/IToolbar.cs b/ScienceAlert.Toolbar/IToolbar.cs deleted file mode 100644 index 6cd68db..0000000 --- a/ScienceAlert.Toolbar/IToolbar.cs +++ /dev/null @@ -1,27 +0,0 @@ -namespace ScienceAlert.Toolbar -{ - public delegate void ToolbarClickHandler(ClickInfo click); - - public interface IToolbar - { - event ToolbarClickHandler OnClick; - - IDrawable Drawable {get;set;} - - bool Important {get;set;} - - bool IsAnimating {get;} - - bool IsNormal {get;} - - bool IsLit {get;} - - void PlayAnimation(); - - void StopAnimation(); - - void SetUnlit(); - - void SetLit(); - } -} diff --git a/ScienceAlert.Toolbar/ToolbarManager.cs b/ScienceAlert.Toolbar/ToolbarManager.cs deleted file mode 100644 index b375ded..0000000 --- a/ScienceAlert.Toolbar/ToolbarManager.cs +++ /dev/null @@ -1,53 +0,0 @@ -namespace ScienceAlert.Toolbar -{ - public class ToolbarManager : IToolbarManager - { - private static bool? toolbarAvailable; - private static IToolbarManager instance_; - private object realToolbarManager; - private System.Reflection.MethodInfo addMethod; - private System.Collections.Generic.Dictionary buttons = new System.Collections.Generic.Dictionary(); - private ToolbarTypes types = new ToolbarTypes(); - - public static bool ToolbarAvailable - { - get - { - if (!toolbarAvailable.HasValue) - toolbarAvailable = Instance != null; - return toolbarAvailable.Value; - } - } - - public static IToolbarManager Instance - { - get - { - if (toolbarAvailable == false || instance_ != null) return instance_; - System.Type type = ToolbarTypes.getType("Toolbar.ToolbarManager"); - if (type == null) return instance_; - object value = ToolbarTypes.getStaticProperty(type, "Instance").GetValue(null, null); - instance_ = new ToolbarManager(value); - return instance_; - } - } - - private ToolbarManager(object realToolbarManager) - { - this.realToolbarManager = realToolbarManager; - addMethod = ToolbarTypes.getMethod(types.iToolbarManagerType, "add"); - } - - public IButton add(string ns, string id) - { - object obj = addMethod.Invoke(realToolbarManager, new object[] - { - ns, - id - }); - IButton button = new Button(obj, types); - buttons.Add(obj, button); - return button; - } - } -} diff --git a/ScienceAlert.Toolbar/ToolbarTypes.cs b/ScienceAlert.Toolbar/ToolbarTypes.cs deleted file mode 100644 index 332dcf9..0000000 --- a/ScienceAlert.Toolbar/ToolbarTypes.cs +++ /dev/null @@ -1,49 +0,0 @@ -using System.Linq; - -namespace ScienceAlert.Toolbar -{ - internal class ToolbarTypes - { - internal readonly System.Type iToolbarManagerType; - - internal readonly System.Type functionVisibilityType; - - internal readonly System.Type functionDrawableType; - - internal readonly ButtonTypes button; - - internal ToolbarTypes() - { - iToolbarManagerType = getType("Toolbar.IToolbarManager"); - functionVisibilityType = getType("Toolbar.FunctionVisibility"); - functionDrawableType = getType("Toolbar.FunctionDrawable"); - System.Type type = getType("Toolbar.IButton"); - button = new ButtonTypes(type); - } - - internal static System.Type getType(string name) - { - return AssemblyLoader.loadedAssemblies.SelectMany((AssemblyLoader.LoadedAssembly a) => a.assembly.GetExportedTypes()).SingleOrDefault((System.Type t) => t.FullName == name); - } - - internal static System.Reflection.PropertyInfo getProperty(System.Type type, string name) - { - return type.GetProperty(name, System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public); - } - - internal static System.Reflection.PropertyInfo getStaticProperty(System.Type type, string name) - { - return type.GetProperty(name, System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public); - } - - internal static System.Reflection.EventInfo getEvent(System.Type type, string name) - { - return type.GetEvent(name, System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public); - } - - internal static System.Reflection.MethodInfo getMethod(System.Type type, string name) - { - return type.GetMethod(name, System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public); - } - } -} diff --git a/ScienceAlert.Windows/DraggableDebugWindow.cs b/ScienceAlert.Windows/DraggableDebugWindow.cs deleted file mode 100644 index 380e2a3..0000000 --- a/ScienceAlert.Windows/DraggableDebugWindow.cs +++ /dev/null @@ -1,36 +0,0 @@ -using ReeperCommon; -using UnityEngine; - -namespace ScienceAlert.Windows -{ - internal class DraggableDebugWindow : DraggableWindow - { - protected override Rect Setup() - { - Title = "Debug"; - Skin = Settings.Skin; - Settings.Instance.OnSave += new Settings.Callback(AboutToSave); - LoadFrom(Settings.Instance.additional.GetNode("DebugWindow") ?? new ConfigNode()); - Log.Debug("ALERT:DraggableDebugWindow.Setup"); - return new Rect(windowRect.x, windowRect.y, 256f, 128f); - } - - private void AboutToSave() - { - Log.Debug("ALERT:DraggableDebugWindow.AboutToSave"); - SaveInto(Settings.Instance.additional.GetNode("DebugWindow") ?? Settings.Instance.additional.AddNode("DebugWindow")); - } - - protected override void DrawUI() - { - GUILayout.BeginVertical(GUILayout.ExpandHeight(true), GUILayout.MinHeight(128f)); - GUILayout.Label("Biome: to be implemented", GUILayout.MinWidth(256f)); - GUILayout.EndVertical(); - } - - protected override void OnCloseClick() - { - Visible = false; - } - } -} diff --git a/ScienceAlert.Windows/DraggableExperimentList.cs b/ScienceAlert.Windows/DraggableExperimentList.cs deleted file mode 100644 index dca73ec..0000000 --- a/ScienceAlert.Windows/DraggableExperimentList.cs +++ /dev/null @@ -1,118 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using ReeperCommon; -using ScienceAlert.Experiments; -using UnityEngine; - -namespace ScienceAlert.Windows -{ - class DraggableExperimentList : DraggableWindow - { - private const string WindowTitle = "Available Experiments"; - - public ExperimentManager manager; - public BiomeFilter biomeFilter; - public ScanInterface scanInterface; - - private bool adjustedSkin; - - protected override Rect Setup() - { - Title = "Available Experiments"; - ShrinkHeightToFit = true; - Skin = Instantiate(Settings.Skin); // we'll be altering it a little bit to make sure the buttons are the right size - Settings.Instance.OnSave += AboutToSave; - LoadFrom(Settings.Instance.additional.GetNode("ExperimentWindow") ?? new ConfigNode()); - return new Rect(windowRect.x, windowRect.y, 256f, 128f); - } - - private void AboutToSave() - { - SaveInto(Settings.Instance.additional.GetNode("ExperimentWindow") ?? Settings.Instance.additional.AddNode("ExperimentWindow")); - } - - private void LateUpdate() - { - if (FlightGlobals.ActiveVessel != null) - if (Settings.Instance.DisplayCurrentBiome) - { - // if SCANsat is enabled, don't show biome names for unscanned areas - if (Settings.Instance.ScanInterfaceType == Settings.ScanInterface.ScanSat && scanInterface != null) - { - if (!scanInterface.HaveScanData(FlightGlobals.ActiveVessel.latitude, FlightGlobals.ActiveVessel.longitude, FlightGlobals.ActiveVessel.mainBody)) - { - Title = "Data not found"; - return; - } - } - Title = GetBiomeString(); - return; - } - Title = WindowTitle; // default experiment window title - } - - private string GetBiomeString() - { - string biome = Title; - if (biomeFilter.GetCurrentBiome(out biome)) - { - return biome; - } - return WindowTitle; - } - - protected new void OnGUI() - { - if (!adjustedSkin) - { - Skin.window.stretchHeight = true; - List experimentTitles = new List(); - ResearchAndDevelopment.GetExperimentIDs().ForEach(id => experimentTitles.Add(ResearchAndDevelopment.GetExperiment(id).experimentTitle)); - Skin.button.fixedWidth = Mathf.Max(64f, experimentTitles.Max(title => - { - float minWidth = 0f; - float maxWidth = 0f; - Skin.button.CalcMinMaxWidth(new GUIContent(title + " (123.4)"), out minWidth, out maxWidth); - return maxWidth; - })); - adjustedSkin = true; - } - base.OnGUI(); - } - - protected override void DrawUI() - { - GUILayout.BeginVertical(); - { - var observers = manager.Observers; - - if (observers.All(eo => !eo.Available)) - { - GUILayout.Label("(no experiments available)"); - } - else - { - //----------------------------------------------------- - // Experiment list - //----------------------------------------------------- - foreach (var observer in observers) - if (observer.Available) - { - var content = new GUIContent(observer.ExperimentTitle); - if (Settings.Instance.ShowReportValue) content.text += $" ({observer.NextReportValue:0.#})"; - if (!GUILayout.Button(content, Settings.Skin.button, GUILayout.ExpandHeight(false))) continue; - Log.Debug("Deploying {0}", observer.ExperimentTitle); - AudioPlayer.Audio.PlayUI("click2"); - observer.Deploy(); - } - } - } - GUILayout.EndVertical(); - } - - protected override void OnCloseClick() - { - Visible = false; - } - } -} diff --git a/ScienceAlert.Windows/DraggableOptionsWindow.cs b/ScienceAlert.Windows/DraggableOptionsWindow.cs deleted file mode 100644 index 2f055ea..0000000 --- a/ScienceAlert.Windows/DraggableOptionsWindow.cs +++ /dev/null @@ -1,621 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using ReeperCommon; -using ScienceAlert.Experiments; -using ScienceAlert.ProfileData; -using ScienceAlert.Toolbar; -using UnityEngine; - -namespace ScienceAlert.Windows -{ - public class DraggableOptionsWindow : DraggableWindow - { - internal enum OpenPane - { - None, - AdditionalOptions, - LoadProfiles - } - - private Vector2 scrollPos = default(Vector2); - private Vector2 additionalScrollPos = default(Vector2); - private Vector2 profileScrollPos = Vector2.zero; - private readonly Dictionary experimentIds = new Dictionary(); - private readonly List filterList = new List(); - private string thresholdValue = "0"; - private OpenPane submenu; - public ScienceAlert scienceAlert; - public ExperimentManager manager; - private Texture2D collapseButton = new Texture2D(24, 24); - private Texture2D expandButton = new Texture2D(24, 24); - private Texture2D openButton = new Texture2D(24, 24); - private Texture2D saveButton = new Texture2D(24, 24); - private Texture2D deleteButton = new Texture2D(24, 24); - private Texture2D renameButton = new Texture2D(24, 24); - private readonly Texture2D blackPixel = new Texture2D(1, 1); - private GUISkin whiteLabel; - private System.Globalization.NumberFormatInfo formatter; - private GUIStyle miniLabelLeft; - private GUIStyle miniLabelRight; - private GUIStyle miniLabelCenter; - private AudioPlayer audio; - internal string editText = string.Empty; - internal string lockName = string.Empty; - internal Profile editProfile; - internal PopupDialog popup; - internal string badChars = "()[]?'\":#$%^&*~;\n\t\r!@,.{}/<>"; - - protected override Rect Setup() - { - formatter = (System.Globalization.NumberFormatInfo)System.Globalization.NumberFormatInfo.CurrentInfo.Clone(); - formatter.CurrencySymbol = string.Empty; - formatter.CurrencyDecimalDigits = 2; - formatter.NumberDecimalDigits = 2; - formatter.PercentDecimalDigits = 2; - audio = AudioPlayer.Audio; - if (audio == null) - Log.Debug("[ScienceAlert]:DraggableOptionsWindow: Failed to find AudioPlayer instance"); - - filterList.Add(new GUIContent("Unresearched")); - filterList.Add(new GUIContent("Not maxed")); - filterList.Add(new GUIContent("< 50% collected")); - filterList.Add(new GUIContent("< 90% collected")); - - openButton = ResourceUtil.GetEmbeddedTexture("Textures.btnOpen.png"); - deleteButton = ResourceUtil.GetEmbeddedTexture("Textures.btnDelete.png"); - renameButton = ResourceUtil.GetEmbeddedTexture("Textures.btnRename.png"); - saveButton = ResourceUtil.GetEmbeddedTexture("Textures.btnSave.png"); - expandButton = ResourceUtil.GetEmbeddedTexture("Textures.btnExpand.png"); - collapseButton = Instantiate(expandButton); - ResourceUtil.FlipTexture(collapseButton, true, true); - collapseButton.Compress(false); - expandButton.Compress(false); - - blackPixel.SetPixel(0, 0, Color.black); - blackPixel.Apply(); - blackPixel.filterMode = FilterMode.Bilinear; - whiteLabel = Instantiate(Settings.Skin); - whiteLabel.label.onNormal.textColor = Color.white; - whiteLabel.toggle.onNormal.textColor = Color.white; - whiteLabel.label.onActive.textColor = Color.white; - submenu = OpenPane.None; - Title = "ScienceAlert Options"; - miniLabelLeft = new GUIStyle(Skin.label) {fontSize = 10}; - miniLabelLeft.normal.textColor = miniLabelLeft.onNormal.textColor = Color.white; - miniLabelRight = new GUIStyle(miniLabelLeft) {alignment = TextAnchor.MiddleRight}; - miniLabelCenter = new GUIStyle(miniLabelLeft) {alignment = TextAnchor.MiddleCenter}; - Settings.Instance.OnSave += OnAboutToSave; - OnVisibilityChange += OnVisibilityChanged; - GameEvents.onVesselChange.Add(OnVesselChanged); - LoadFrom(Settings.Instance.additional.GetNode("OptionsWindow") ?? new ConfigNode()); - return new Rect(windowRect.x, windowRect.y, 324f, Screen.height / 5 * 3); - } - - protected new void OnDestroy() - { - base.OnDestroy(); - OnVisibilityChange -= OnVisibilityChanged; - } - - private void OnVisibilityChanged(bool tf) - { - if (tf) - { - OnProfileChanged(); - return; - } - if (manager == null) return; - manager.RebuildObserverList(); - } - - public void OnProfileChanged() - { - if (ScienceAlertProfileManager.ActiveProfile == null) return; - thresholdValue = ScienceAlertProfileManager.ActiveProfile.ScienceThreshold.ToString("F2", formatter); - List experimentIDs = ResearchAndDevelopment.GetExperimentIDs(); - IOrderedEnumerable orderedEnumerable = from expid in experimentIDs - orderby ResearchAndDevelopment.GetExperiment(expid).experimentTitle - select expid; - experimentIds.Clear(); - using (IEnumerator enumerator = orderedEnumerable.GetEnumerator()) - { - while (enumerator.MoveNext()) - { - string current = enumerator.Current; - experimentIds.Add(current, (int)System.Convert.ChangeType(ScienceAlertProfileManager.ActiveProfile[current].Filter, - ScienceAlertProfileManager.ActiveProfile[current].Filter.GetTypeCode())); - } - } - } - - private void OnVesselChanged(Vessel vessel) - { - OnVisibilityChanged(Visible); - } - - protected override void OnCloseClick() - { - Visible = false; - } - - private void OnAboutToSave() - { - SaveInto(Settings.Instance.additional.GetNode("OptionsWindow") ?? Settings.Instance.additional.AddNode("OptionsWindow")); - } - - protected override void DrawUI() - { - GUILayout.BeginVertical(GUILayout.ExpandWidth(true), GUILayout.Height(Screen.height / 5 * 3)); - GUILayout.Label(new GUIContent("Global Warp Settings"), GUILayout.ExpandWidth(true)); - Settings.Instance.GlobalWarp = (Settings.WarpSetting)GUILayout.SelectionGrid((int)Settings.Instance.GlobalWarp, new[] - { - new GUIContent("By Experiment"), - new GUIContent("Globally on"), - new GUIContent("Globally off") - }, 3, GUILayout.ExpandWidth(false)); - GUILayout.Label(new GUIContent("Global Alert Sound"), GUILayout.ExpandWidth(true)); - Settings.Instance.SoundNotification = (Settings.SoundNotifySetting)GUILayout.SelectionGrid((int)Settings.Instance.SoundNotification, new[] - { - new GUIContent("By Experiment"), - new GUIContent("Always"), - new GUIContent("Never") - }, 3, GUILayout.ExpandWidth(false)); - GUILayout.Space(4f); - GUILayout.BeginHorizontal(); - GUILayout.Label(new GUIContent("Additional Options")); - GUILayout.FlexibleSpace(); - if (AudibleButton(new GUIContent(submenu == OpenPane.AdditionalOptions ? collapseButton : expandButton))) - { - submenu = submenu == OpenPane.AdditionalOptions ? OpenPane.None : OpenPane.AdditionalOptions; - } - GUILayout.EndHorizontal(); - switch (submenu) - { - case OpenPane.None: - DrawProfileSettings(); - break; - case OpenPane.AdditionalOptions: - DrawAdditionalOptions(); - break; - case OpenPane.LoadProfiles: - DrawProfileList(); - break; - } - GUILayout.EndVertical(); - } - - private void DrawAdditionalOptions() - { - additionalScrollPos = GUILayout.BeginScrollView(additionalScrollPos, GUILayout.ExpandHeight(true)); - GUILayout.Space(4f); - GUILayout.BeginVertical(GUILayout.ExpandHeight(true)); - GUILayout.Box("User Interface Settings", GUILayout.ExpandWidth(true)); - GUILayout.BeginHorizontal(GUILayout.ExpandWidth(true)); - GUILayout.Label("Globally Enable Animation", GUILayout.ExpandWidth(true)); - Settings.Instance.FlaskAnimationEnabled = AudibleToggle(Settings.Instance.FlaskAnimationEnabled, string.Empty, null, new[] - { - GUILayout.ExpandWidth(false) - }); - if (!Settings.Instance.FlaskAnimationEnabled && scienceAlert.Button.IsAnimating) - { - scienceAlert.Button.SetLit(); - } - GUILayout.EndHorizontal(); - Settings.Instance.ShowReportValue = AudibleToggle(Settings.Instance.ShowReportValue, "Display Report Value"); - Settings.Instance.DisplayCurrentBiome = AudibleToggle(Settings.Instance.DisplayCurrentBiome, "Display Biome in Experiment List"); - Settings.Instance.EvaReportOnTop = AudibleToggle(Settings.Instance.EvaReportOnTop, "List EVA Report first"); - GUILayout.Label("Window Opacity"); - GUILayout.BeginHorizontal(); - GUILayout.Label("Less", miniLabelLeft); - GUILayout.FlexibleSpace(); - GUILayout.Label("More", miniLabelRight); - GUILayout.EndHorizontal(); - Settings.Instance.WindowOpacity = (int)GUILayout.HorizontalSlider(Settings.Instance.WindowOpacity, 0f, 255f, GUILayout.ExpandWidth(true), GUILayout.MaxHeight(16f)); - GUILayout.Space(8f); - GUILayout.Box("Third-party Integration Options", GUILayout.ExpandWidth(true)); - GUILayout.BeginHorizontal(GUILayout.ExpandWidth(true)); - Settings.ScanInterface scanInterfaceType = Settings.Instance.ScanInterfaceType; - Color color = GUI.color; - if (!SCANsatInterface.IsAvailable()) - { - GUI.color = Color.red; - } - bool flag = AudibleToggle(Settings.Instance.ScanInterfaceType == Settings.ScanInterface.ScanSat, "Enable SCANsat integration", null, new[] - { - GUILayout.ExpandWidth(true) - }); - GUI.color = color; - if (flag && scanInterfaceType != Settings.ScanInterface.ScanSat && !SCANsatInterface.IsAvailable()) - { - PopupDialog.SpawnPopupDialog(new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f), - "SCANsat Not Found", "SCANsat was not found. You must install SCANsat to use this feature.", "", "Okay", - false, HighLogic.UISkin); - flag = false; - } - Settings.Instance.ScanInterfaceType = flag ? Settings.ScanInterface.ScanSat : Settings.ScanInterface.None; - scienceAlert.ScanInterfaceType = Settings.Instance.ScanInterfaceType; - GUILayout.EndHorizontal(); - GUILayout.BeginHorizontal(GUILayout.ExpandWidth(true)); - //Settings.ToolbarInterface toolbarInterfaceType = Settings.Instance.ToolbarInterfaceType; - Color color2 = GUI.color; - if (!ToolbarManager.ToolbarAvailable) - { - GUI.color = Color.red; - } - //bool flag2 = AudibleToggle(Settings.Instance.ToolbarInterfaceType == Settings.ToolbarInterface.BlizzyToolbar, "Use Blizzy toolbar"); - GUI.color = color2; - //if (flag2 && toolbarInterfaceType != Settings.ToolbarInterface.BlizzyToolbar && !ToolbarManager.ToolbarAvailable) - //{ - // PopupDialog.SpawnPopupDialog("Blizzy Toolbar Not Found", - // "Blizzy's toolbar was not found. You must install Blizzy's toolbar to use this feature.", - // "Okay", false, Settings.Skin); //??? - // flag2 = false; - //} - //Settings.Instance.ToolbarInterfaceType = (flag2 ? Settings.ToolbarInterface.BlizzyToolbar : Settings.ToolbarInterface.ApplicationLauncher); - if (scienceAlert.ToolbarType != Settings.Instance.ToolbarInterfaceType) - { - scienceAlert.ToolbarType = Settings.Instance.ToolbarInterfaceType; - } - GUILayout.EndHorizontal(); - GUILayout.Box("Crewed Vessel Settings", GUILayout.ExpandWidth(true)); - bool checkSurfaceSampleNotEva = Settings.Instance.CheckSurfaceSampleNotEva; - Settings.Instance.CheckSurfaceSampleNotEva = AudibleToggle(checkSurfaceSampleNotEva, "Track surface sample in vessel"); - if (checkSurfaceSampleNotEva != Settings.Instance.CheckSurfaceSampleNotEva) - { - manager.RebuildObserverList(); - } - GUILayout.EndVertical(); - GUI.skin = Settings.Skin; - GUILayout.EndScrollView(); - } - - private void DrawProfileSettings() - { - if (ScienceAlertProfileManager.HasActiveProfile) - { - GUILayout.BeginHorizontal(); - GUILayout.Box($"Profile: {ScienceAlertProfileManager.ActiveProfile.DisplayName}", GUILayout.ExpandHeight(true), GUILayout.ExpandWidth(true)); - if (AudibleButton(new GUIContent(renameButton), GUILayout.MaxWidth(24f))) - { - SpawnRenamePopup(ScienceAlertProfileManager.ActiveProfile); - } - GUI.enabled = ScienceAlertProfileManager.ActiveProfile.modified; - if (AudibleButton(new GUIContent(saveButton), GUILayout.MaxWidth(24f))) - { - SpawnSavePopup(); - } - GUI.enabled = true; - if (AudibleButton(new GUIContent(openButton), GUILayout.MaxWidth(24f))) - { - submenu = OpenPane.LoadProfiles; - } - GUILayout.EndHorizontal(); - scrollPos = GUILayout.BeginScrollView(scrollPos, Settings.Skin.scrollView); - GUI.skin = Settings.Skin; - GUILayout.Space(4f); - GUI.SetNextControlName("ThresholdHeader"); - GUILayout.Box("Alert Threshold"); - GUILayout.BeginHorizontal(GUILayout.ExpandWidth(true), GUILayout.MinHeight(14f)); - if (ScienceAlertProfileManager.ActiveProfile.ScienceThreshold > 0f) - { - GUILayout.Label( - $"Alert Threshold: {ScienceAlertProfileManager.ActiveProfile.ScienceThreshold.ToString("F2", formatter)}"); - } - else - { - Color color = GUI.color; - GUI.color = XKCDColors.Salmon; - GUILayout.Label("(disabled)"); - GUI.color = color; - } - GUILayout.FlexibleSpace(); - if (string.IsNullOrEmpty(thresholdValue)) - { - thresholdValue = ScienceAlertProfileManager.ActiveProfile.scienceThreshold.ToString("F2", formatter); - } - GUI.SetNextControlName("ThresholdText"); - string s = GUILayout.TextField(thresholdValue, GUILayout.MinWidth(60f)); - if (Event.current.keyCode == KeyCode.Escape) - { - GUI.FocusControl("ThresholdHeader"); - } - if (GUI.GetNameOfFocusedControl() == "ThresholdText") - { - try - { - float scienceThreshold = float.Parse(s, formatter); - ScienceAlertProfileManager.ActiveProfile.ScienceThreshold = scienceThreshold; - thresholdValue = s; - } - catch (System.Exception) - { - // ignored - } - if (!InputLockManager.IsLocked(ControlTypes.ACTIONS_ALL)) - { - InputLockManager.SetControlLock(ControlTypes.ACTIONS_ALL, "ScienceAlertThreshold"); - } - } - else if (InputLockManager.GetControlLock("ScienceAlertThreshold") != ControlTypes.None) - { - InputLockManager.RemoveControlLock("ScienceAlertThreshold"); - } - GUILayout.EndHorizontal(); - GUILayout.Space(3f); - var num = GUILayout.HorizontalSlider(ScienceAlertProfileManager.ActiveProfile.ScienceThreshold, 0f, 100f, GUILayout.ExpandWidth(true), GUILayout.Height(14f)); - if (num != ScienceAlertProfileManager.ActiveProfile.scienceThreshold) - { - ScienceAlertProfileManager.ActiveProfile.ScienceThreshold = num; - thresholdValue = num.ToString("F2", formatter); - } - GUILayout.BeginHorizontal(GUILayout.ExpandWidth(true), GUILayout.MaxHeight(10f)); - GUILayout.Label("0", miniLabelLeft); - GUILayout.FlexibleSpace(); - GUILayout.Label("Science Amount", miniLabelCenter); - GUILayout.FlexibleSpace(); - GUILayout.Label("100", miniLabelRight); - GUILayout.EndHorizontal(); - GUILayout.Space(10f); - List list = new List(experimentIds.Keys); - foreach (string current in list) - { - GUILayout.Space(4f); - ExperimentSettings experimentSettings = ScienceAlertProfileManager.ActiveProfile[current]; - string experimentTitle = ResearchAndDevelopment.GetExperiment(current).experimentTitle; - GUILayout.Box(experimentTitle, GUILayout.ExpandWidth(true)); - experimentSettings.Enabled = AudibleToggle(experimentSettings.Enabled, "Enabled"); - experimentSettings.AnimationOnDiscovery = AudibleToggle(experimentSettings.AnimationOnDiscovery, "Animation on discovery"); - experimentSettings.SoundOnDiscovery = AudibleToggle(experimentSettings.SoundOnDiscovery, "Sound on discovery"); - experimentSettings.StopWarpOnDiscovery = AudibleToggle(experimentSettings.StopWarpOnDiscovery, "Stop warp on discovery"); - GUILayout.Label(new GUIContent("Filter Method"), GUILayout.ExpandWidth(true), GUILayout.MinHeight(24f)); - int num2 = experimentIds[current]; - experimentIds[current] = AudibleSelectionGrid(num2, ref experimentSettings); - } - GUILayout.EndScrollView(); - return; - } - GUI.color = Color.red; - GUILayout.Label("No profile active"); - } - - private void DrawProfileList() - { - profileScrollPos = GUILayout.BeginScrollView(profileScrollPos, Settings.Skin.scrollView); - if (ScienceAlertProfileManager.Count > 0) - { - GUILayout.Label("Select a profile to load"); - GUILayout.Box(blackPixel, GUILayout.ExpandWidth(true), GUILayout.MinHeight(1f), GUILayout.MaxHeight(3f)); - GUILayout.Space(4f); - Dictionary profiles = ScienceAlertProfileManager.Profiles; - DrawProfileList_ListItem(ScienceAlertProfileManager.DefaultProfile); - using (Dictionary.ValueCollection.Enumerator enumerator = profiles.Values.GetEnumerator()) - { - while (enumerator.MoveNext()) - { - Profile current = enumerator.Current; - if (current != ScienceAlertProfileManager.DefaultProfile) - { - DrawProfileList_ListItem(current); - } - } - goto IL_F1; - } - } - GUILayout.FlexibleSpace(); - GUILayout.Box("No profiles saved", GUILayout.MinHeight(64f)); - GUILayout.FlexibleSpace(); - IL_F1: - GUILayout.Space(10f); - GUILayout.BeginHorizontal(); - GUILayout.FlexibleSpace(); - if (AudibleButton(new GUIContent("Cancel", "Cancel load operation"))) - { - submenu = OpenPane.None; - } - GUILayout.EndHorizontal(); - GUILayout.EndScrollView(); - } - - private void DrawProfileList_ListItem(Profile profile) - { - GUILayout.BeginHorizontal(); - GUILayout.Box(profile.name, GUILayout.ExpandWidth(true)); - GUI.enabled = profile != ScienceAlertProfileManager.DefaultProfile; - if (AudibleButton(new GUIContent(renameButton), GUILayout.MaxWidth(24f), GUILayout.MinWidth(24f))) - { - SpawnRenamePopup(profile); - } - GUI.enabled = true; - if (AudibleButton(new GUIContent(openButton), GUILayout.MaxWidth(24f), GUILayout.MinWidth(24f))) - { - SpawnOpenPopup(profile); - } - GUI.enabled = profile != ScienceAlertProfileManager.DefaultProfile; - if (AudibleButton(new GUIContent(deleteButton), GUILayout.MaxWidth(24f), GUILayout.MinWidth(24f))) - { - SpawnDeletePopup(profile); - } - GUI.enabled = true; - GUILayout.EndHorizontal(); - } - - private bool AudibleToggle(bool value, string content, GUIStyle style = null, GUILayoutOption[] options = null) - { - return AudibleToggle(value, new GUIContent(content), style, options); - } - - private bool AudibleToggle(bool value, GUIContent content, GUIStyle style = null, GUILayoutOption[] options = null) - { - bool flag = GUILayout.Toggle(value, content, style == null ? Settings.Skin.toggle : style, options); - if (flag != value) - audio.PlayUI("click1"); - return flag; - } - - private int AudibleSelectionGrid(int currentValue, ref ExperimentSettings settings) - { - int num = GUILayout.SelectionGrid(currentValue, filterList.ToArray(), 2, GUILayout.ExpandWidth(true)); - if (num == currentValue) return num; - audio.PlayUI("click1"); - settings.Filter = (ExperimentSettings.FilterMethod)num; - return num; - } - - private bool AudibleButton(GUIContent content, params GUILayoutOption[] options) - { - bool flag = GUILayout.Button(content, options); - if (!flag) return false; - audio.PlayUI("click1"); - return true; - } - - private void SpawnSavePopup() - { - editText = ScienceAlertProfileManager.ActiveProfile.name; - LockControls("ScienceAlertSavePopup"); - - DialogGUIBase[] dialogOptions = new DialogGUIBase[2]; - dialogOptions[0] = new DialogGUIButton("SAVE", SaveCurrentProfile); - dialogOptions[1] = new DialogGUIButton("CANCEL", DismissPopup); - - popup = PopupDialog.SpawnPopupDialog(new MultiOptionDialog( "", "", $"SAVE '{editText}'?", - HighLogic.UISkin, dialogOptions), - false, HighLogic.UISkin); - } - - private void SaveCurrentProfile() - { - if (popup != null) - popup.Dismiss(); - else - editText = ScienceAlertProfileManager.ActiveProfile.name; - - if (ScienceAlertProfileManager.HaveStoredProfile(editText)) - { - popup = PopupDialog.SpawnPopupDialog(new MultiOptionDialog("","", - $"Profile '{editText}' already exists!", HighLogic.UISkin, - new DialogGUIButton("Overwrite", SaveCurrentProfileOverwrite), - new DialogGUIButton("Cancel", DismissPopup)), - false, HighLogic.UISkin); - } - else - SaveCurrentProfileOverwrite(); // save to go ahead and save since no existing profile with this key exists - } - - private void SaveCurrentProfileOverwrite() - { - ScienceAlertProfileManager.StoreActiveProfile(editText); - Settings.Instance.Save(); - DismissPopup(); - } - - private void SpawnRenamePopup(Profile target) - { - editProfile = target; - editText = target.name; - LockControls("ScienceAlertRenamePopup"); - - DialogGUIBase[] dialogOptions = new DialogGUIBase[3]; - dialogOptions[0] = new DialogGUITextInput(editText, false, 22, s =>{ editText = s;return s;},30f); - dialogOptions[1] = new DialogGUIButton("RENAME", RenameTargetProfile); - dialogOptions[2] = new DialogGUIButton("CANCEL", DismissPopup); - - popup = PopupDialog.SpawnPopupDialog( - new MultiOptionDialog("", "", $"Rename '{target.name}' to:", - HighLogic.UISkin, dialogOptions), - false, HighLogic.UISkin); - } - - private void RenameTargetProfile() - { - if (editProfile.modified || !ScienceAlertProfileManager.HaveStoredProfile(editProfile.name)) - { - RenameTargetProfileOverwrite(); - } - else - { - if (ScienceAlertProfileManager.HaveStoredProfile(editText)) - { - popup.Dismiss(); - popup = PopupDialog.SpawnPopupDialog( - new MultiOptionDialog(string.Empty, $"'{editText}' already exists. Overwrite?", "RenameTargetProfile", HighLogic.UISkin, - new DialogGUIButton("Yes", RenameTargetProfileOverwrite), - new DialogGUIButton("No", DismissPopup)), - false, HighLogic.UISkin); - return; - } - RenameTargetProfileOverwrite(); - } - SpawnSavePopup(); - DismissPopup(); - } - - private void RenameTargetProfileOverwrite() - { - if (!editProfile.modified && ScienceAlertProfileManager.HaveStoredProfile(editProfile.name)) - { - ScienceAlertProfileManager.RenameProfile(editProfile.name, editText); - if (!ScienceAlertProfileManager.ActiveProfile.modified) - { - ScienceAlertProfileManager.ActiveProfile.name = editText; - } - } - else - { - editProfile.name = editText; - editProfile.modified = true; - } - DismissPopup(); - } - - private void SpawnDeletePopup(Profile target) - { - editProfile = target; - LockControls("ScienceAlertDeletePopup"); - popup = PopupDialog.SpawnPopupDialog( - new MultiOptionDialog("","", $"Are you sure you want to\ndelete '{target.name}'?", HighLogic.UISkin, - new DialogGUIButton("Confirm", DeleteTargetProfile), - new DialogGUIButton("Cancel", DismissPopup)), - false, HighLogic.UISkin); - } - - private void DeleteTargetProfile() - { - DismissPopup(); - ScienceAlertProfileManager.DeleteProfile(editProfile.name); - } - - private void SpawnOpenPopup(Profile target) - { - editProfile = target; - LockControls("ScienceAlertOpenPopup"); - popup = PopupDialog.SpawnPopupDialog( - new MultiOptionDialog(string.Empty, $"Load '{editProfile.name}'?\nUnsaved settings will be lost.", - "ScienceAlertOpenPopup", HighLogic.UISkin, - new DialogGUIButton("Confirm", LoadTargetProfile), - new DialogGUIButton("Cancel", DismissPopup)), - false, HighLogic.UISkin); - } - - private void LoadTargetProfile() - { - DismissPopup(); - if (!ScienceAlertProfileManager.AssignAsActiveProfile(editProfile.Clone())) return; - submenu = OpenPane.None; - OnVisibilityChanged(Visible); - } - - private void LockControls(string lockName) - { - this.lockName = lockName; - InputLockManager.SetControlLock(ControlTypes.ACTIONS_ALL, lockName); - } - - private void DismissPopup() - { - if (popup) popup.Dismiss(); - InputLockManager.RemoveControlLock(lockName); - lockName = string.Empty; - } - } -} diff --git a/ScienceAlert.Windows/WindowEventLogic.cs b/ScienceAlert.Windows/WindowEventLogic.cs deleted file mode 100644 index 7ba690e..0000000 --- a/ScienceAlert.Windows/WindowEventLogic.cs +++ /dev/null @@ -1,107 +0,0 @@ -using ReeperCommon; -using ScienceAlert.Experiments; - -using UnityEngine; - -namespace ScienceAlert.Windows -{ - /// - /// This class controls which window(s) are shown when - /// - class WindowEventLogic : MonoBehaviour - { - DraggableExperimentList experimentList; - DraggableOptionsWindow optionsWindow; - DraggableDebugWindow debugWindow; - ScienceAlert scienceAlert; - - private void Awake() - { - Log.Normal("Customizing DraggableWindow"); - - DraggableWindow.CloseTexture = ResourceUtil.LocateTexture("ScienceAlert.Resources.btnClose.png"); - DraggableWindow.LockTexture = ResourceUtil.LocateTexture("ScienceAlert.Resources.btnLock.png"); - DraggableWindow.UnlockTexture = ResourceUtil.LocateTexture("ScienceAlert.Resources.btnUnlock.png"); - DraggableWindow.ButtonHoverBackground = ResourceUtil.LocateTexture("ScienceAlert.Resources.btnBackground.png"); - DraggableWindow.ButtonSound = "click1"; - - scienceAlert = GetComponent(); - - optionsWindow = new GameObject("ScienceAlert.OptionsWindow").AddComponent(); - optionsWindow.scienceAlert = GetComponent(); - optionsWindow.manager = GetComponent(); - experimentList = new GameObject("ScienceAlert.ExperimentList").AddComponent(); - experimentList.biomeFilter = GetComponent(); - experimentList.manager = GetComponent(); - debugWindow = new GameObject("ScienceAlert.DebugWindow").AddComponent(); - optionsWindow.Visible = experimentList.Visible = debugWindow.Visible = false; - } - - private void Start() - { - scienceAlert.OnToolbarButtonChanged += OnToolbarChanged; - scienceAlert.OnScanInterfaceChanged += OnInterfaceChanged; - optionsWindow.OnVisibilityChange += OnWindowVisibilityChanged; - experimentList.OnVisibilityChange += OnWindowVisibilityChanged; - debugWindow.OnVisibilityChange += OnWindowVisibilityChanged; - OnToolbarChanged(); - OnInterfaceChanged(); - } - - private void OnToolbarChanged() - { - scienceAlert.Button.OnClick += OnToolbarClick; - } - - private void OnInterfaceChanged() - { - experimentList.scanInterface = GetComponent(); - } - - private void OnToolbarClick(Toolbar.ClickInfo clickInfo) - { - if (optionsWindow.Visible || experimentList.Visible || debugWindow.Visible) - { - Log.Debug("WindowEventLogic: Hiding window(s)"); - optionsWindow.Visible = experimentList.Visible = debugWindow.Visible = false; - AudioPlayer.Audio.PlayUI("click1", 0.5f); - } - else - { - switch (clickInfo.button) - { - case 0: // left click, show experiment list - experimentList.Visible = true; - break; - case 1: // right click, show options window - optionsWindow.Visible = true; - break; - case 2: // middle click, show debug window (for AppLauncher this is alt + right click) - debugWindow.Visible = true; - break; - } - AudioPlayer.Audio.PlayUI("click1", 0.5f); - } - } - - private void OnWindowVisibilityChanged(bool tf) - { - //if (scienceAlert.ToolbarType == Settings.ToolbarInterface.ApplicationLauncher) - // if (tf) - // { - // GetComponent().button.SetTrue(false); - // } - // else - // { - // if (!experimentList.Visible && !optionsWindow.Visible && !debugWindow.Visible) - // GetComponent().button.SetFalse(false); - // } - } - - private void Update() - { - var mouse = new Vector2(Input.mousePosition.x, Screen.height - Input.mousePosition.y); - DraggableWindow[] windows = new DraggableWindow[] { optionsWindow, experimentList, debugWindow }; - } - } -} \ No newline at end of file diff --git a/ScienceAlert.sln b/ScienceAlert.sln index 21228c5..b5b4853 100644 --- a/ScienceAlert.sln +++ b/ScienceAlert.sln @@ -1,9 +1,21 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 14 -VisualStudioVersion = 14.0.25420.1 +# Visual Studio 15 +VisualStudioVersion = 15.0.27428.2005 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ScienceAlert", "ScienceAlert.csproj", "{835DC165-6942-4C55-A84E-A9DE6B6FA840}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ScienceAlert", "Source\ScienceAlert.csproj", "{835DC165-6942-4C55-A84E-A9DE6B6FA840}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{984B4270-F25C-4849-A5FE-99FAEF4371A0}" + ProjectSection(SolutionItems) = preProject + buildRelease.bat = buildRelease.bat + ChangeLog.txt = ChangeLog.txt + deploy.bat = deploy.bat + Instructions.txt = Instructions.txt + jenkins.txt = jenkins.txt + LICENSE.txt = LICENSE.txt + README.md = README.md + ScienceAlert.version = ScienceAlert.version + EndProjectSection EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -19,4 +31,7 @@ Global GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {946F06D8-0307-4AAA-855A-C1B9E37D9677} + EndGlobalSection EndGlobal diff --git a/ScienceAlert.version b/ScienceAlert.version new file mode 100644 index 0000000..45c2002 --- /dev/null +++ b/ScienceAlert.version @@ -0,0 +1,30 @@ +{ + "NAME": "ScienceAlert", + "URL": "https://raw.githubusercontent.com/linuxgurugamer/ScienceAlert/refs/heads/master/ScienceAlert.version", + "DOWNLOAD": "https://spacedock.info/mod/1886/ScienceAlert%20ReAlerted", + "GITHUB": { + "USERNAME": "linuxgurugamer", + "REPOSITORY": "ScienceAlert" + }, + "VERSION": { + "MAJOR": 1, + "MINOR": 9, + "PATCH": 20, + "BUILD": 5 + }, + "KSP_VERSION": { + "MAJOR": 1, + "MINOR": 12, + "PATCH": 5 + }, + "KSP_VERSION_MIN": { + "MAJOR": 1, + "MINOR": 9, + "PATCH": 0 + }, + "KSP_VERSION_MAX": { + "MAJOR": 1, + "MINOR": 12, + "PATCH": 99 + } +} diff --git a/ScienceAlert.version.1-12-3 b/ScienceAlert.version.1-12-3 new file mode 100644 index 0000000..9a45381 --- /dev/null +++ b/ScienceAlert.version.1-12-3 @@ -0,0 +1,25 @@ +{ + "NAME": "ScienceAlert", + "URL": "http://ksp.spacetux.net/avc/ScienceAlert", + "DOWNLOAD": "https://spacedock.info/mod/1886/ScienceAlert%20ReAlerted", + "GITHUB": { + "USERNAME": "linuxgurugamer", + "REPOSITORY": "ScienceAlert" + }, + "VERSION": { + "MAJOR": 1, + "MINOR": 9, + "PATCH": 10, + "BUILD": 1 + }, + "KSP_VERSION": { + "MAJOR": 1, + "MINOR": 12, + "PATCH": 3 + }, + "KSP_VERSION_MIN": { + "MAJOR": 1, + "MINOR": 9, + "PATCH": 0 + } +} diff --git a/ScienceAlert/Button.cs b/ScienceAlert/Button.cs deleted file mode 100644 index c73911b..0000000 --- a/ScienceAlert/Button.cs +++ /dev/null @@ -1,206 +0,0 @@ -using System; -using ScienceAlert.Toolbar; -using UnityEngine; - -namespace ScienceAlert -{ - internal class Button : IButton - { - private object realButton; - private ToolbarTypes types; - private Delegate realClickHandler; - private Delegate realMouseEnterHandler; - private Delegate realMouseLeaveHandler; - private IVisibility visibility_; - private IDrawable drawable_; - public event ClickHandler OnClick; - public event MouseEnterHandler OnMouseEnter; - public event MouseLeaveHandler OnMouseLeave; - - public string Text - { - get - { - return (string)types.button.textProperty.GetValue(realButton, null); - } - set - { - types.button.textProperty.SetValue(realButton, value, null); - } - } - - public Color TextColor - { - get - { - return (Color)types.button.textColorProperty.GetValue(realButton, null); - } - set - { - types.button.textColorProperty.SetValue(realButton, value, null); - } - } - - public string TexturePath - { - get - { - return (string)types.button.texturePathProperty.GetValue(realButton, null); - } - set - { - types.button.texturePathProperty.SetValue(realButton, value, null); - } - } - - public string ToolTip - { - get - { - return (string)types.button.toolTipProperty.GetValue(realButton, null); - } - set - { - types.button.toolTipProperty.SetValue(realButton, value, null); - } - } - - public bool Visible - { - get - { - return (bool)types.button.visibleProperty.GetValue(realButton, null); - } - set - { - types.button.visibleProperty.SetValue(realButton, value, null); - } - } - - public IVisibility Visibility - { - get - { - return visibility_; - } - set - { - object value2 = null; - if (value != null) - { - Type arg_40_0 = types.functionVisibilityType; - object[] array = new object[1]; - array[0] = new Func(() => value.Visible); - value2 = Activator.CreateInstance(arg_40_0, array); - } - types.button.visibilityProperty.SetValue(realButton, value2, null); - visibility_ = value; - } - } - - public bool EffectivelyVisible => (bool)types.button.effectivelyVisibleProperty.GetValue(realButton, null); - - public bool Enabled - { - get - { - return (bool)types.button.enabledProperty.GetValue(realButton, null); - } - set - { - types.button.enabledProperty.SetValue(realButton, value, null); - } - } - - public bool Important - { - get - { - return (bool)types.button.importantProperty.GetValue(realButton, null); - } - set - { - types.button.importantProperty.SetValue(realButton, value, null); - } - } - - public IDrawable Drawable - { - get - { - return drawable_; - } - set - { - object value2 = null; - if (value != null) - { - Type arg_5A_0 = types.functionDrawableType; - object[] array = new object[2]; - array[0] = new Action(delegate - { - value.Update(); - }); - array[1] = new Func((Vector2 pos) => value.Draw(pos)); - value2 = Activator.CreateInstance(arg_5A_0, array); - } - types.button.drawableProperty.SetValue(realButton, value2, null); - drawable_ = value; - } - } - - internal Button(object realButton, ToolbarTypes types) - { - this.realButton = realButton; - this.types = types; - realClickHandler = attachEventHandler(types.button.onClickEvent, "clicked", realButton); - realMouseEnterHandler = attachEventHandler(types.button.onMouseEnterEvent, "mouseEntered", realButton); - realMouseLeaveHandler = attachEventHandler(types.button.onMouseLeaveEvent, "mouseLeft", realButton); - } - - private Delegate attachEventHandler(System.Reflection.EventInfo @event, string methodName, object realButton) - { - System.Reflection.MethodInfo method = GetType().GetMethod(methodName, System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic); - Delegate @delegate = Delegate.CreateDelegate(@event.EventHandlerType, this, method); - @event.AddEventHandler(realButton, @delegate); - return @delegate; - } - - private void clicked(object realEvent) - { - if (OnClick != null) - { - OnClick(new ClickEvent(realEvent, this)); - } - } - - private void mouseEntered(object realEvent) - { - if (OnMouseEnter != null) - { - OnMouseEnter(new MouseEnterEvent(this)); - } - } - - private void mouseLeft(object realEvent) - { - if (OnMouseLeave != null) - { - OnMouseLeave(new MouseLeaveEvent(this)); - } - } - - public void Destroy() - { - detachEventHandler(types.button.onClickEvent, realClickHandler, realButton); - detachEventHandler(types.button.onMouseEnterEvent, realMouseEnterHandler, realButton); - detachEventHandler(types.button.onMouseLeaveEvent, realMouseLeaveHandler, realButton); - types.button.destroyMethod.Invoke(realButton, null); - } - - private void detachEventHandler(System.Reflection.EventInfo @event, Delegate d, object realButton) - { - @event.RemoveEventHandler(realButton, d); - } - } -} diff --git a/ScienceAlert/ButtonTypes.cs b/ScienceAlert/ButtonTypes.cs deleted file mode 100644 index 8d80a80..0000000 --- a/ScienceAlert/ButtonTypes.cs +++ /dev/null @@ -1,44 +0,0 @@ -using System; -using System.Reflection; -using ScienceAlert.Toolbar; - -namespace ScienceAlert -{ - internal class ButtonTypes - { - internal readonly Type iButtonType; - internal readonly PropertyInfo textProperty; - internal readonly PropertyInfo textColorProperty; - internal readonly PropertyInfo texturePathProperty; - internal readonly PropertyInfo toolTipProperty; - internal readonly PropertyInfo visibleProperty; - internal readonly PropertyInfo visibilityProperty; - internal readonly PropertyInfo effectivelyVisibleProperty; - internal readonly PropertyInfo enabledProperty; - internal readonly PropertyInfo importantProperty; - internal readonly PropertyInfo drawableProperty; - internal readonly EventInfo onClickEvent; - internal readonly EventInfo onMouseEnterEvent; - internal readonly EventInfo onMouseLeaveEvent; - internal readonly MethodInfo destroyMethod; - - internal ButtonTypes(System.Type iButtonType) - { - this.iButtonType = iButtonType; - textProperty = ToolbarTypes.getProperty(iButtonType, "Text"); - textColorProperty = ToolbarTypes.getProperty(iButtonType, "TextColor"); - texturePathProperty = ToolbarTypes.getProperty(iButtonType, "TexturePath"); - toolTipProperty = ToolbarTypes.getProperty(iButtonType, "ToolTip"); - visibleProperty = ToolbarTypes.getProperty(iButtonType, "Visible"); - visibilityProperty = ToolbarTypes.getProperty(iButtonType, "Visibility"); - effectivelyVisibleProperty = ToolbarTypes.getProperty(iButtonType, "EffectivelyVisible"); - enabledProperty = ToolbarTypes.getProperty(iButtonType, "Enabled"); - importantProperty = ToolbarTypes.getProperty(iButtonType, "Important"); - drawableProperty = ToolbarTypes.getProperty(iButtonType, "Drawable"); - onClickEvent = ToolbarTypes.getEvent(iButtonType, "OnClick"); - onMouseEnterEvent = ToolbarTypes.getEvent(iButtonType, "OnMouseEnter"); - onMouseLeaveEvent = ToolbarTypes.getEvent(iButtonType, "OnMouseLeave"); - destroyMethod = ToolbarTypes.getMethod(iButtonType, "Destroy"); - } - } -} diff --git a/ScienceAlert/GameScenesVisibility.cs b/ScienceAlert/GameScenesVisibility.cs deleted file mode 100644 index 42147f1..0000000 --- a/ScienceAlert/GameScenesVisibility.cs +++ /dev/null @@ -1,20 +0,0 @@ -using ScienceAlert.Toolbar; - -namespace ScienceAlert -{ - public class GameScenesVisibility : IVisibility - { - private object realGameScenesVisibility; - - private System.Reflection.PropertyInfo visibleProperty; - - public bool Visible => (bool)visibleProperty.GetValue(realGameScenesVisibility, null); - - public GameScenesVisibility(params GameScenes[] gameScenes) - { - System.Type type = ToolbarTypes.getType("Toolbar.GameScenesVisibility"); - realGameScenesVisibility = System.Activator.CreateInstance(type, gameScenes); - visibleProperty = ToolbarTypes.getProperty(type, "Visible"); - } - } -} diff --git a/ScienceAlert/Interfaces.cs b/ScienceAlert/Interfaces.cs deleted file mode 100644 index cb06b48..0000000 --- a/ScienceAlert/Interfaces.cs +++ /dev/null @@ -1,65 +0,0 @@ -using UnityEngine; - -namespace ScienceAlert -{ - public class DefaultScanInterface : ScanInterface - { - } - - public class ScanInterface : MonoBehaviour - { - public virtual bool HaveScanData(double lat, double lon, CelestialBody body) - { - return true; - } - } - - public interface IVisibility - { - bool Visible - { - get; - } - } - - public interface IToolbarManager - { - IButton add(string ns, string id); - } - - public interface IDrawable - { - void Update(); - - Vector2 Draw(Vector2 position); - } - - public interface IButton - { - event ClickHandler OnClick; - event MouseEnterHandler OnMouseEnter; - event MouseLeaveHandler OnMouseLeave; - - string Text {get;set;} - - Color TextColor {get;set;} - - string TexturePath {get;set;} - - string ToolTip {get;set;} - - bool Visible {get;set;} - - IVisibility Visibility {get;set;} - - bool EffectivelyVisible {get;} - - bool Enabled {get;set;} - - bool Important {get;set;} - - IDrawable Drawable {get;set;} - - void Destroy(); - } -} diff --git a/ScienceAlert/MagicDataTransmitter.cs b/ScienceAlert/MagicDataTransmitter.cs deleted file mode 100644 index 3cf543c..0000000 --- a/ScienceAlert/MagicDataTransmitter.cs +++ /dev/null @@ -1,236 +0,0 @@ -using ReeperCommon; -using System.Collections.Generic; -using System.Linq; - -namespace ScienceAlert -{ - public class NonexistentTransmitterException : System.Exception - { - } - - public class MagicDataTransmitter : PartModule, IScienceDataTransmitter - { - private Dictionary, Callback>> realTransmitters = new Dictionary, Callback>>(); - private Dictionary, Callback>>> toBeTransmitted = new Dictionary, Callback>>>(); - internal StorageCache cacheOwner; - - float IScienceDataTransmitter.DataRate => 3.40282347E+38f; - - double IScienceDataTransmitter.DataResourceCost => 0.0; - - public List QueuedData - { - get - { - List list = new List(); - bool flag = false; - try - { - foreach (KeyValuePair, Callback>> current in realTransmitters) - { - if (current.Key == null) - { - Log.Debug("[ScienceAlert]:MagicDataTransmitter: Encountered a bad transmitter value."); - flag = true; - } - else - { - if (!current.Key.IsBusy() && current.Value.Key != null) - { - current.Value.Key.Clear(); - } - if (current.Value.Key != null) - { - list.AddRange(current.Value.Key); - } - list.AddRange(toBeTransmitted[current.Key].SelectMany(transmitterData => transmitterData.Key)); - } - } - } - catch (System.Exception ex) - { - flag = true; - Log.Debug("[ScienceAlert]:Exception occurred in MagicDataTransmitter.QueuedData: {0}", ex); - } - if (flag) - { - Log.Warning("Resetting MagicDataTransmitter due to bad transmitter key or value"); - cacheOwner.ScheduleRebuild(); - } - return list; - } - } - - public void Start() - { - Log.Debug("ALERT:MagicDataTransmitter started"); - RefreshTransmitterQueues(new List, Callback>>()); - } - - public List, Callback>> GetQueuedData() - { - return toBeTransmitted.Values.SelectMany(q => q.ToArray()).ToList(); - } - - public void RefreshTransmitterQueues(List, Callback>> queuedData) - { - if (queuedData == null) - { - throw new System.ArgumentNullException("queuedData"); - } - Dictionary, Callback>> dictionary = new Dictionary, Callback>>(realTransmitters); - realTransmitters.Clear(); - toBeTransmitted.Clear(); - List list = (from tx in FlightGlobals.ActiveVessel.FindPartModulesImplementing() - where !(tx is MagicDataTransmitter) - select tx).ToList(); - if (!list.Any()) - { - Destroy(this); - cacheOwner.ScheduleRebuild(); - } - foreach (IScienceDataTransmitter current in list) - { - realTransmitters.Add(current, default(KeyValuePair, Callback>)); - toBeTransmitted.Add(current, new Queue, Callback>>()); - } - Log.Debug("ALERT:MagicDataTransmitter has found {0} useable transmitters", list.Count); - foreach (IScienceDataTransmitter current2 in dictionary.Keys) - { - if (realTransmitters.ContainsKey(current2)) - { - realTransmitters[current2] = dictionary[current2]; - } - } - if (!queuedData.Any()) return; - foreach (KeyValuePair, Callback> current3 in queuedData) - { - TransmitData(current3.Key, current3.Value); - } - } - - private void BeginTransmissionWithRealTransmitter(IScienceDataTransmitter transmitter, List science, Callback callback) - { - if (transmitter == null) - { - throw new System.ArgumentNullException("transmitter"); - } - if (science == null) - { - throw new System.ArgumentNullException("science"); - } - if ((PartModule)transmitter == null) - { - TransmitData(science, callback); - throw new NonexistentTransmitterException(); - } - Log.Debug(string.Concat("Beginning real transmission of ", science.Count, " science reports on transmitter ", ((PartModule)transmitter).part.flightID)); - if (callback != null) return; - transmitter.TransmitData(science); - } - - public void Update() - { - Dictionary, Callback>>>.KeyCollection keys = toBeTransmitted.Keys; - try - { - foreach (IScienceDataTransmitter current in keys) - { - if (toBeTransmitted[current].Count > 0 && !current.IsBusy() && current.CanTransmit()) - { - KeyValuePair, Callback> value = toBeTransmitted[current].Dequeue(); - Log.Debug("ALERT:Dispatching " + value.Key.Count + " science data entries to transmitter"); - realTransmitters[current] = value; - BeginTransmissionWithRealTransmitter(current, value.Key, value.Value); - } - } - } - catch (NonexistentTransmitterException) - { - Log.Warning("MagicDataTransmitter: Nonexistent transmitter encountered. Rescanning vessel and re-queuing transmissions"); - realTransmitters.Clear(); - RefreshTransmitterQueues(GetQueuedData()); - if (!realTransmitters.Any()) - { - Log.Warning("MagicDataTransmitter: No real transmitters found. Data will stay queued. If the vessel is switched or scenes are changed before it is dispatched, it will be lost."); - } - } - catch (KeyNotFoundException) - { - Log.Debug("[ScienceAlert]:MagicDataTransmitter appears to be out of date. Any queued data might have been lost."); - toBeTransmitted.Clear(); - realTransmitters.Clear(); - cacheOwner.ScheduleRebuild(); - } - } - - public override void OnSave(ConfigNode node) - { - node.ClearData(); - } - - public override void OnLoad(ConfigNode node) - { - } - - private void QueueTransmission(List data, IScienceDataTransmitter transmitter, Callback callback) - { - if (data.Count == 0) - { - return; - } - Log.Debug("ALERT:Queued " + data.Count + " science reports for transmission"); - toBeTransmitted[transmitter].Enqueue(new KeyValuePair, Callback>(new List(data), callback)); - } - - void IScienceDataTransmitter.TransmitData(List data) - { - TransmitData(data, null); - } - - public void TransmitData(List dataQueue, Callback callback) - { - Log.Debug("ALERT:MagicTransmitter: received {0} ScienceData entries", dataQueue.Count); - Log.Debug(callback == null ? "ALERT: with no callback" : "ALERT:With callback"); - List list = new List(); - foreach (KeyValuePair, Callback>> current in realTransmitters) - { - list.Add(current.Key); - } - if (list.Any()) - { - list = (from potential in list - orderby ScienceUtil.GetTransmitterScore(potential) - select potential).ToList(); - QueueTransmission(dataQueue, list.First(), callback); - return; - } - Log.Debug("[ScienceAlert]:MagicDataTransmitter: Did not find any real transmitters"); - } - - bool IScienceDataTransmitter.IsBusy() - { - return false; - } - - bool IScienceDataTransmitter.CanTransmit() - { - return realTransmitters.Any(pair => pair.Key.CanTransmit()); - } - - private void TransmissionComplete(IScienceDataTransmitter transmitter, Callback original) - { - Log.Debug("ALERT:Received TransmissionComplete callback from " + transmitter.GetType().Name); - if (original != null) - { - original(); - } - } - - public override string ToString() - { - return - $"MagicDataTransmitter attached to {FlightGlobals.ActiveVessel.rootPart.name}; {QueuedData.Count} entries in queue"; - } - } -} diff --git a/ScienceAlert/MouseEvents.cs b/ScienceAlert/MouseEvents.cs deleted file mode 100644 index c626a0e..0000000 --- a/ScienceAlert/MouseEvents.cs +++ /dev/null @@ -1,45 +0,0 @@ -namespace ScienceAlert -{ - public delegate void MouseEnterHandler(MouseEnterEvent e); - public delegate void MouseLeaveHandler(MouseLeaveEvent e); - public delegate void ClickHandler(ClickEvent e); - - public class ClickEvent : System.EventArgs - { - public readonly IButton Button; - - public readonly int MouseButton; - - internal ClickEvent(object realEvent, IButton button) - { - System.Type type = realEvent.GetType(); - Button = button; - MouseButton = (int)type.GetField("MouseButton", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public).GetValue(realEvent); - } - } - - public class MouseEnterEvent : MouseMoveEvent - { - internal MouseEnterEvent(IButton button) : base(button) - { - } - } - - public class MouseLeaveEvent : MouseMoveEvent - { - internal MouseLeaveEvent(IButton button) : base(button) - { - } - } - - public abstract class MouseMoveEvent : System.EventArgs - { - public readonly IButton button; - - internal MouseMoveEvent(IButton button) - { - this.button = button; - } - } -} - diff --git a/ScienceAlert/PopupMenuDrawable.cs b/ScienceAlert/PopupMenuDrawable.cs deleted file mode 100644 index c8fc07c..0000000 --- a/ScienceAlert/PopupMenuDrawable.cs +++ /dev/null @@ -1,79 +0,0 @@ -using System; -using ScienceAlert.Toolbar; -using UnityEngine; - -namespace ScienceAlert -{ - public class PopupMenuDrawable : IDrawable - { - private object realPopupMenuDrawable; - - private System.Reflection.MethodInfo updateMethod; - - private System.Reflection.MethodInfo drawMethod; - - private System.Reflection.MethodInfo addOptionMethod; - - private System.Reflection.MethodInfo addSeparatorMethod; - - private System.Reflection.MethodInfo destroyMethod; - - private System.Reflection.EventInfo onAnyOptionClickedEvent; - - public event Action OnAnyOptionClicked - { - add - { - onAnyOptionClickedEvent.AddEventHandler(realPopupMenuDrawable, value); - } - remove - { - onAnyOptionClickedEvent.RemoveEventHandler(realPopupMenuDrawable, value); - } - } - - public PopupMenuDrawable() - { - Type type = ToolbarTypes.getType("Toolbar.PopupMenuDrawable"); - realPopupMenuDrawable = Activator.CreateInstance(type, null); - updateMethod = ToolbarTypes.getMethod(type, "Update"); - drawMethod = ToolbarTypes.getMethod(type, "Draw"); - addOptionMethod = ToolbarTypes.getMethod(type, "AddOption"); - addSeparatorMethod = ToolbarTypes.getMethod(type, "AddSeparator"); - destroyMethod = ToolbarTypes.getMethod(type, "Destroy"); - onAnyOptionClickedEvent = ToolbarTypes.getEvent(type, "OnAnyOptionClicked"); - } - - public void Update() - { - updateMethod.Invoke(realPopupMenuDrawable, null); - } - - public Vector2 Draw(Vector2 position) - { - return (Vector2)drawMethod.Invoke(realPopupMenuDrawable, new object[] - { - position - }); - } - - public IButton AddOption(string text) - { - object realButton = addOptionMethod.Invoke(realPopupMenuDrawable, new object[] - { - text - }); - return new Button(realButton, new ToolbarTypes()); - } - - public void AddSeparator() - { - addSeparatorMethod.Invoke(realPopupMenuDrawable, null); - } - - public void Destroy() - { - destroyMethod.Invoke(realPopupMenuDrawable, null); - } - } -} diff --git a/ScienceAlert/SCANsatInterface.cs b/ScienceAlert/SCANsatInterface.cs deleted file mode 100644 index aa76eda..0000000 --- a/ScienceAlert/SCANsatInterface.cs +++ /dev/null @@ -1,64 +0,0 @@ -using ReeperCommon; -using System.Linq; - -namespace ScienceAlert -{ - internal class SCANsatInterface : ScanInterface - { - private delegate bool IsCoveredDelegate(double lat, double lon, CelestialBody body, int mask); - private const string SCANutilTypeName = "SCANsat.SCANUtil"; - private static IsCoveredDelegate _isCovered = (double lat, double lon, CelestialBody body, int mask) => true; - private static System.Reflection.MethodInfo _method; - private static bool _ran; - - private void OnDestroy() - { - _ran = false; - } - - public override bool HaveScanData(double lat, double lon, CelestialBody body) - { - return _isCovered(lat, lon, body, 8); - } - - public static bool IsAvailable() - { - if (_method != null && _isCovered != null) return true; - if (_ran) return false; - _ran = true; - try - { - System.Type type = AssemblyLoader.loadedAssemblies.SelectMany((AssemblyLoader.LoadedAssembly loaded) => loaded.assembly.GetExportedTypes()).SingleOrDefault((System.Type t) => t.FullName == "SCANsat.SCANUtil"); - bool result; - if (type == null) - { - result = false; - return result; - } - _method = type.GetMethod("isCovered", new System.Type[] - { - typeof(double), - typeof(double), - typeof(CelestialBody), - typeof(int) - }); - if (_method == null) - { - result = false; - return result; - } - _isCovered = (IsCoveredDelegate)System.Delegate.CreateDelegate(typeof(IsCoveredDelegate), _method); - Log.Debug(_isCovered == null - ? "[ScienceAlert]:SCANsatInterface: Failed to create method delegate" - : "[ScienceAlert]:SCANsatInterface: Interface available"); - result = _isCovered != null; - return result; - } - catch (System.Exception ex) - { - Log.Debug("[ScienceAlert]:Exception in SCANsatInterface.IsAvailable: {0}", ex); - } - return false; - } - } -} diff --git a/ScienceAlert/ScienceAlert.cs b/ScienceAlert/ScienceAlert.cs deleted file mode 100644 index cea8ee3..0000000 --- a/ScienceAlert/ScienceAlert.cs +++ /dev/null @@ -1,148 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using ReeperCommon; -using ScienceAlert.Experiments; -using ScienceAlert.ProfileData; -using ScienceAlert.Toolbar; -using ScienceAlert.Windows; -using UnityEngine; - -namespace ScienceAlert -{ - [KSPAddon(KSPAddon.Startup.Flight, false)] - - public class ScienceAlert : MonoBehaviour - { - private IToolbar button; - private ScanInterface scanInterface; - public DraggableOptionsWindow optionsWindow; - public static ScienceAlert Instance; - private Settings.ToolbarInterface buttonInterfaceType; - private Settings.ScanInterface scanInterfaceType; - public event Callback OnScanInterfaceChanged = delegate{}; - public event Callback OnToolbarButtonChanged = delegate{}; - - public IToolbar Button => button; - - public Settings.ToolbarInterface ToolbarType - { - get - { - return buttonInterfaceType; - } - set - { - if (value == buttonInterfaceType && button != null) return; - switch (buttonInterfaceType) - { - case Settings.ToolbarInterface.BlizzyToolbar: - Destroy(button as BlizzyInterface); - break; - } - button = null; - switch (value) - { - case Settings.ToolbarInterface.BlizzyToolbar: - if (ToolbarManager.ToolbarAvailable) - button = gameObject.AddComponent(); - break; - } - buttonInterfaceType = value; - OnToolbarButtonChanged(); - } - } - - public Settings.ScanInterface ScanInterfaceType - { - get - { - return scanInterfaceType; - } - set - { - if (value == scanInterfaceType && scanInterface != null) return; - if (scanInterface != null) - { - DestroyImmediate(GetComponent()); - } - try - { - switch (value) - { - case Settings.ScanInterface.None: - scanInterface = gameObject.AddComponent(); - break; - case Settings.ScanInterface.ScanSat: - if (!SCANsatInterface.IsAvailable()) - { - ScanInterfaceType = Settings.ScanInterface.None; - return; - } - scanInterface = gameObject.AddComponent(); - break; - default: - throw new NotImplementedException("Unrecognized interface type"); - } - } - catch (Exception ex) - { - Log.Debug("[ScienceAlert]:ScienceAlert.ScanInterfaceType failed with exception {0}", ex); - ScanInterfaceType = Settings.ScanInterface.None; - return; - } - scanInterfaceType = value; - OnScanInterfaceChanged(); - } - } - - private IEnumerator Start() - { - while (ResearchAndDevelopment.Instance == null) - { - yield return 0; - } - while (FlightGlobals.ActiveVessel == null) - { - yield return 0; - } - while (!FlightGlobals.ready) - { - yield return 0; - } - Instance = this; - while (ScienceAlertProfileManager.Instance == null || !ScienceAlertProfileManager.Instance.Ready) - { - yield return 0; - } - - try - { - ScienceExperiment experiment = ResearchAndDevelopment.GetExperiment("asteroidSample"); - if (experiment != null) - { - experiment.experimentTitle = "Sample (Asteroid)"; - } - } - catch (KeyNotFoundException) - { - Destroy(this); - } - gameObject.AddComponent().LoadSoundsFrom(ConfigUtil.GetDllDirectoryPath() + "/sounds"); - gameObject.AddComponent(); - gameObject.AddComponent(); - gameObject.AddComponent(); - ScanInterfaceType = Settings.Instance.ScanInterfaceType; - ToolbarType = Settings.Instance.ToolbarInterfaceType; - } - - public void OnDestroy() - { - if (Button != null) - { - Button.Drawable = null; - } - Settings.Instance.Save(); - } - } -} diff --git a/ScienceAlert/Settings.cs b/ScienceAlert/Settings.cs deleted file mode 100644 index a02daf7..0000000 --- a/ScienceAlert/Settings.cs +++ /dev/null @@ -1,263 +0,0 @@ -using System; -using System.IO; -using ReeperCommon; -using UnityEngine; -using Object = UnityEngine.Object; - -namespace ScienceAlert -{ - public class Settings - { - public delegate void SaveCallback(ConfigNode node); - - public delegate void Callback(); - - public enum WarpSetting - { - ByExperiment, - GlobalOn, - GlobalOff - } - - public enum SoundNotifySetting - { - ByExperiment, - Always, - Never - } - - public enum ScanInterface - { - None, - ScanSat - } - - public enum ToolbarInterface - { - //ApplicationLauncher, - BlizzyToolbar - } - - private static Settings instance; - - [DoNotSerialize] - private readonly string ConfigPath = ConfigUtil.GetDllDirectoryPath() + "/settings.cfg"; - - [DoNotSerialize] - private GUISkin skin; - - public ConfigNode additional = new ConfigNode("config"); - - [DoNotSerialize] - private int windowOpacity = 255; - - [DoNotSerialize] - protected ScanInterface Interface; - - [DoNotSerialize] - protected ToolbarInterface ToolbarType; - - [DoNotSerialize] - public event Callback OnSave = delegate {}; - - [DoNotSerialize] - public event SaveCallback OnLoad = delegate {}; - - public static Settings Instance - { - get - { - if (instance != null) return instance; - instance = new Settings(); - return instance; - } - } - - public static GUISkin Skin => Instance.skin; - - public bool DebugMode {get;private set;} - - [Subsection("General")] - public WarpSetting GlobalWarp {get;set;} - - public float TimeWarpCheckThreshold {get;private set;} - - [Subsection("General")] - public SoundNotifySetting SoundNotification {get;set;} - - [Subsection("General")] - public double EvaAtmospherePressureWarnThreshold {get;private set;} - - [Subsection("General")] - public float EvaAtmosphereVelocityWarnThreshold {get; private set;} - - [Subsection("UserInterface")] public bool ShowReportValue {get;set;} - - [Subsection("UserInterface")] public bool DisplayCurrentBiome {get;set;} - - [Subsection("UserInterface")] - public bool FlaskAnimationEnabled {get;set;} - - [Subsection("UserInterface")] public float StarFlaskFrameRate {get;private set;} - - public int WindowOpacity - { - get - { - return windowOpacity; - } - set - { - Texture2D background = skin.window.normal.background; - windowOpacity = value; - Color32[] pixels = background.GetPixels32(); - for (int i = 0; i < pixels.Length; i++) - { - pixels[i].a = (byte)Mathf.Clamp(windowOpacity, 0, 255); - } - background.SetPixels32(pixels); - background.Apply(); - } - } - - public bool EvaReportOnTop {get;set;} - - [Subsection("CrewedVesselSettings")] - public bool CheckSurfaceSampleNotEva {get;set;} - - public ScanInterface ScanInterfaceType - { - get - { - ScanInterface @interface = Interface; - if (@interface != ScanInterface.ScanSat) - { - return Interface; - } - if (SCANsatInterface.IsAvailable()) - { - return Interface; - } - return ScanInterface.None; - } - set - { - Interface = value; - } - } - - public ToolbarInterface ToolbarInterfaceType - { - get - { - ToolbarInterface toolbarType = ToolbarType; - if (toolbarType != ToolbarInterface.BlizzyToolbar) - { - return ToolbarType; - } - //if (!ToolbarManager.ToolbarAvailable) - //{ - // return Settings.ToolbarInterface.ApplicationLauncher; - //} - return ToolbarInterface.BlizzyToolbar; - } - set - { - ToolbarType = value; - } - } - - private Settings() - { - skin = Object.Instantiate(HighLogic.Skin); - skin.button = new GUIStyle(skin.button); - skin.button.fixedHeight = 24f; - skin.button.padding = new RectOffset - { - left = 2, - right = 2, - top = 0, - bottom = 0 - }; - skin.button.border = new RectOffset - { - left = 2, - right = 2, - top = 1, - bottom = 1 - }; - skin.toggle.border.top = skin.toggle.border.bottom = skin.toggle.border.left = skin.toggle.border.right = 0; - skin.toggle.margin = new RectOffset(5, 0, 0, 0); - skin.toggle.padding = new RectOffset - { - left = 5, - top = 3, - right = 3, - bottom = 3 - }; - skin.box.alignment = TextAnchor.MiddleCenter; - skin.box.padding = new RectOffset(2, 2, 8, 5); - skin.box.contentOffset = new Vector2(0f, 0f); - skin.horizontalSlider.margin = new RectOffset(); - skin.window = new GUIStyle(skin.GetStyle("window")); - skin.window.onActive.background = skin.window.onFocused.background = skin.window.onNormal.background = skin.window.onHover.background = skin.window.active.background = skin.window.focused.background = skin.window.hover.background = skin.window.normal.background = skin.window.normal.background.CreateReadable(); - WindowOpacity = 255; - skin.window.onNormal.textColor = skin.window.normal.textColor = XKCDColors.Green_Yellow; - skin.window.onHover.textColor = skin.window.hover.textColor = XKCDColors.YellowishOrange; - skin.window.onFocused.textColor = skin.window.focused.textColor = Color.red; - skin.window.onActive.textColor = skin.window.active.textColor = Color.blue; - skin.window.fontSize = 12; - EvaAtmospherePressureWarnThreshold = 0.00035; - EvaAtmosphereVelocityWarnThreshold = 30f; - ScanInterfaceType = ScanInterface.None; - ShowReportValue = false; - EvaReportOnTop = false; - CheckSurfaceSampleNotEva = false; - DisplayCurrentBiome = false; - StarFlaskFrameRate = 24f; - FlaskAnimationEnabled = true; - TimeWarpCheckThreshold = 5f; - DraggableWindow.DefaultSkin = skin; - Load(); - } - - - public void Load() - { - Log.Debug("[ScienceAlert]:Loading settings from {0}", ConfigPath); - if (!File.Exists(ConfigPath)) - { - Log.Debug("[ScienceAlert]:Failed to find settings file {0}", ConfigPath); - Save(); - return; - } - ConfigNode configNode = ConfigNode.Load(ConfigPath); - if (configNode == null) - { - Log.Debug("[ScienceAlert]:Failed to load {0}", ConfigPath); - return; - } - configNode.CreateObjectFromConfigEx(this); - Log.LoadFrom(configNode); - OnLoad(additional); - } - - public void Save() - { - ConfigNode configNode = null; - try - { - OnSave(); - configNode = this.CreateConfigFromObjectEx() ?? new ConfigNode(); - } - catch (Exception ex) - { - Log.Debug("[ScienceAlert]:Exception while creating ConfigNode from settings: {0}", ex); - } - Log.SaveInto(configNode); - if (configNode.CountNodes <= 0 && configNode.CountValues <= 0) return; - Log.Debug("[ScienceAlert]:Saving settings to {0}", ConfigPath); - configNode.Save(ConfigPath); - } - } -} diff --git a/ScienceAlert/StorageCache.cs b/ScienceAlert/StorageCache.cs deleted file mode 100644 index 30f7ee7..0000000 --- a/ScienceAlert/StorageCache.cs +++ /dev/null @@ -1,184 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using ReeperCommon; -using System.Linq; -using UnityEngine; - -namespace ScienceAlert -{ - public class StorageCache : MonoBehaviour - { - protected List storage = new List(); - protected MagicDataTransmitter magicTransmitter; - protected Vessel vessel; - - public int StorageContainerCount => storage.Count; - - public bool IsBusy - { - get; - private set; - } - - public void Start() - { - GameEvents.onVesselChange.Add(OnVesselChange); - GameEvents.onVesselWasModified.Add(OnVesselModified); - GameEvents.onVesselDestroy.Add(OnVesselDestroyed); - vessel = FlightGlobals.ActiveVessel; - ScheduleRebuild(); - } - - public void OnDestroy() - { - GameEvents.onVesselDestroy.Remove(OnVesselDestroyed); - GameEvents.onVesselWasModified.Remove(OnVesselModified); - GameEvents.onVesselChange.Remove(OnVesselChange); - RemoveMagicTransmitter(false); - Log.Debug("ALERT:StorageCache destroyed"); - } - - public void OnVesselChange(Vessel v) - { - RemoveMagicTransmitter(); - vessel = v; - ScheduleRebuild(); - } - - public void OnVesselModified(Vessel v) - { - if (vessel != v) - { - OnVesselChange(v); - return; - } - ScheduleRebuild(); - } - - public void OnVesselDestroyed(Vessel v) - { - if (vessel != v) return; - storage = new List(); - magicTransmitter = null; - vessel = null; - } - - public void ScheduleRebuild() - { - if (IsBusy) - { - try - { - StopCoroutine("Rebuild"); - } - catch (Exception) - { - // ignored - } - } - StartCoroutine("Rebuild"); - } - - private IEnumerator Rebuild() - { - IsBusy = true; - storage.Clear(); - List, Callback>> queuedData = magicTransmitter != null ? magicTransmitter.GetQueuedData() : new List, Callback>>(); - magicTransmitter = null; - yield return new WaitForFixedUpdate(); - if (FlightGlobals.ActiveVessel != vessel) - { - RemoveMagicTransmitter(); - } - while (FlightGlobals.ActiveVessel != null && !vessel.loaded || !FlightGlobals.ready) - { - yield return new WaitForFixedUpdate(); - } - if (FlightGlobals.ActiveVessel == null) - { - IsBusy = false; - } - else - { - vessel = FlightGlobals.ActiveVessel; - storage = vessel.FindPartModulesImplementing(); - List source = (from tx in vessel.FindPartModulesImplementing() - where !(tx is MagicDataTransmitter) - select tx).ToList(); - if (source.Any()) - { - magicTransmitter = vessel.rootPart.gameObject.GetComponent(); - if (magicTransmitter != null) - { - magicTransmitter.RefreshTransmitterQueues(queuedData); - } - else - { - magicTransmitter = vessel.rootPart.AddModule("MagicDataTransmitter") as MagicDataTransmitter; - if (magicTransmitter != null) - magicTransmitter.cacheOwner = this; - } - } - else - { - RemoveMagicTransmitter(false); - Log.Debug("ALERT:Vessel {0} has no transmitters; no magic transmitter added", vessel.name); - } - IsBusy = false; - Log.Debug("ALERT:Rebuilt StorageCache"); - } - } - - private void RemoveMagicTransmitter(bool rootOnly = true) - { - magicTransmitter = null; - if (vessel == null || vessel.rootPart == null) return; - try - { - if (vessel.rootPart.Modules.Contains("MagicDataTransmitter")) - { - vessel.rootPart.RemoveModule(vessel.rootPart.Modules.OfType().Single()); - } - if (rootOnly) return; - foreach (Part current in vessel.Parts) - { - if (current.Modules.Contains("MagicDataTransmitter")) - { - current.RemoveModule(current.Modules.OfType().First()); - } - } - } - catch (Exception ex) - { - Log.Warning("RemoveMagicTransmitter: caught exception {0}", ex); - } - } - - public List FindStoredData(string subjectid) - { - List list = new List(); - foreach (IScienceDataContainer current in storage) - { - if (current.GetScienceCount() <= 0) continue; - ScienceData[] data = current.GetData(); - for (int i = 0; i < data.Length; i++) - { - ScienceData scienceData = data[i]; - if (scienceData.subjectID == subjectid) - { - list.Add(scienceData); - } - } - } - if (magicTransmitter == null) return list; - foreach (ScienceData current2 in magicTransmitter.QueuedData) - { - if (current2.subjectID != subjectid) continue; - list.Add(current2); - Log.Debug("ALERT:Found stored data in transmitter queue"); - } - return list; - } - } -} diff --git a/ScienceAlert/Util.cs b/ScienceAlert/Util.cs deleted file mode 100644 index 886f071..0000000 --- a/ScienceAlert/Util.cs +++ /dev/null @@ -1,31 +0,0 @@ -using UnityEngine; - -namespace ScienceAlert -{ - public static class Util - { - public static float CalculateNextReport(this ScienceSubject subject, ScienceExperiment experiment, System.Collections.Generic.List onboard, float xmitScalar = 1f) - { - return GetNextReportValue(subject, experiment, onboard, xmitScalar); - } - - public static float GetNextReportValue(ScienceSubject subject, ScienceExperiment experiment, System.Collections.Generic.List onboard, float xmitScalar = 1f) - { - ScienceData scienceData = new ScienceData - (experiment.baseValue * experiment.dataScale * HighLogic.CurrentGame.Parameters.Career.ScienceGainMultiplier, - xmitScalar, 0f, subject.id, string.Empty); - //scienceData.transmitBonus = ModuleScienceLab.GetBoostForVesselData(FlightGlobals.ActiveVessel, scienceData); ??? - xmitScalar += scienceData.transmitBonus; - if (onboard.Count == 0) - { - return ResearchAndDevelopment.GetScienceValue(experiment.baseValue * experiment.dataScale, subject, xmitScalar) * HighLogic.CurrentGame.Parameters.Career.ScienceGainMultiplier; - } - float num = ResearchAndDevelopment.GetNextScienceValue(experiment.baseValue * experiment.dataScale, subject, xmitScalar) * HighLogic.CurrentGame.Parameters.Career.ScienceGainMultiplier; - if (onboard.Count == 1) - { - return num; - } - return num / Mathf.Pow(4f, (float)(onboard.Count - 1)); - } - } -} diff --git a/Source/AssemblyVersion.cs b/Source/AssemblyVersion.cs new file mode 100644 index 0000000..8d70013 --- /dev/null +++ b/Source/AssemblyVersion.cs @@ -0,0 +1,13 @@ + + + + + + // This code was generated by a tool. Any changes made manually will be lost + // the next time this code is regenerated. + // + + using System.Reflection; + + [assembly: AssemblyVersion("1.9.20.5")] + [assembly: AssemblyFileVersion("1.9.20.5")] diff --git a/Source/AssemblyVersion.tt b/Source/AssemblyVersion.tt new file mode 100644 index 0000000..6442ca8 --- /dev/null +++ b/Source/AssemblyVersion.tt @@ -0,0 +1,100 @@ +<#@ template debug="false" hostspecific="true" language="C#" #> +<#@ import namespace="System.IO" #> +<#@ output extension=".cs" #> + +<# + + // Instructions + // 1. Add a new Text Template to the project + // 2. Copy this file into the new template + // 3. Update the string: versionfile with the complete path to the .version file + // 4. Remove the following line from the file AssemblyInfo.cs (usually located in the "Property" folder inside your C# project): + // [assembly: AssemblyVersion("1.0.0.0")] + // 5. Add the following to the PreBuild steps: + // + // set textTemplatingPath="%ProgramFiles(x86)%\Microsoft Visual Studio\2017\Community\Common7\IDE\texttransform.exe" + // %textTemplatingPath% "$(ProjectDir)AssemblyVersion.tt" + + + int major = 0; + int minor = 0; + int build = 0; + int patch = 0; + bool versionSection = false; + + int i = 0; + int i2 = 0; + string s; + + // For Visual Studio / MSBuild Build-Time Template Resolution + string RootDirectory = System.IO.Path.GetDirectoryName(Host.TemplateFile) + @"\..\"; + + + // + // Update the following with the name of the .version file which is in the root directory + // + string versionfile = RootDirectory + "ScienceAlert.version"; + + if (!File.Exists(versionfile)) + { + Write("File: " + versionfile + " missing\n"); + } + + try + { + foreach (var line in File.ReadAllLines(versionfile)) + { + if (line != null) + { + if (!versionSection) + { + if (line.Contains("\"VERSION\"")) + versionSection = true; + } + else + { + if (line.Contains("}")) + versionSection = false; + i = line.IndexOf(":"); + i2 = line.IndexOf(","); + if (i2 == -1) + i2 = line.Length; + if (i >= 0 && i2 >= 0) + { + s = line.Substring(i + 1, i2 - i - 1); + + if (line.Contains("MAJOR")) + Int32.TryParse(s, out major); + + if (line.Contains("MINOR")) + Int32.TryParse(s, out minor); + + if (line.Contains("PATCH")) + Int32.TryParse(s, out patch); + + if (line.Contains("BUILD")) + Int32.TryParse(s, out build); + } + } + } + } + + } + catch + { + major = 1; + minor = 0; + patch = 0; + build = 0; + } + //Write("File done"); + + #> + // This code was generated by a tool. Any changes made manually will be lost + // the next time this code is regenerated. + // + + using System.Reflection; + + [assembly: AssemblyVersion("<#= major #>.<#= minor #>.<#= patch #>.<#= build #>")] + [assembly: AssemblyFileVersion("<#= major #>.<#= minor #>.<#= patch #>.<#= build #>")] diff --git a/Source/DMModuleScienceAnimateGenericStuff.cs b/Source/DMModuleScienceAnimateGenericStuff.cs new file mode 100644 index 0000000..9d7523d --- /dev/null +++ b/Source/DMModuleScienceAnimateGenericStuff.cs @@ -0,0 +1,76 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using DMModuleScienceAnimateGeneric; +using DMModuleScienceAnimateGeneric_NM; + +namespace ScienceAlert +{ + using Log = ReeperCommon.Log; + public class DMagic_SciAnimGenFactory + { + static internal DMagic_SciAnimGenFactory fetch = null; + + + private Type _tDMModuleScienceAnimate; + private Type _tDMModuleScienceAnimateGeneric; + + + internal DMagic_SciAnimGenFactory() + { + fetch = this; + _tDMModuleScienceAnimate = ReeperCommon.DMagicFactory.getType("DMagic.Part_Modules.DMModuleScienceAnimate"); + _tDMModuleScienceAnimateGeneric = ReeperCommon.DMagicFactory.getType("DMModuleScienceAnimateGeneric_NM.DMModuleScienceAnimateGeneric"); + } + + + public bool inheritsFromOrIsDMModuleScienceAnimate(object o) + { + if (_tDMModuleScienceAnimate == null) + { + return false; + } + return ((o.GetType().IsSubclassOf(_tDMModuleScienceAnimate) || o.GetType() == _tDMModuleScienceAnimate)); + } + + + public bool inheritsFromOrIsDMModuleScienceAnimateGeneric(object o) + { + if (_tDMModuleScienceAnimateGeneric == null) + return false; + return ((o.GetType().IsSubclassOf(_tDMModuleScienceAnimateGeneric) || o.GetType() == _tDMModuleScienceAnimateGeneric)); + } + + + public IEnumerable FindDMAnimateGenericsForExperiment(string experimentId) + { + return FlightGlobals.ActiveVessel.FindPartModulesImplementing().Where(x => inheritsFromOrIsDMModuleScienceAnimateGeneric(x) && x.experimentID == experimentId).ToList(); + } + + + internal bool RunExperiment(string sid, ModuleScienceExperiment exp, bool runSingleUse = true) + { + IScienceDataContainer m = null; + + // If possible run with DMagic new API + IEnumerable lm = FindDMAnimateGenericsForExperiment(sid); + if (lm != null && lm.Any()) + { + m = lm.FirstOrDefault(x => + (int)x.Fields.GetValue("experimentsLimit") > 1 ? DMSciAnimAPI.experimentCanConduct(x) : DMSciAnimAPI.experimentCanConduct(x) && + (x.rerunnable || runSingleUse)); + + if (m != null) + { + DMSciAnimAPI.deployDMExperiment(m, false); + } + + return true; + } + + return false; + } + + } + +} diff --git a/Source/DMagicStuff.cs b/Source/DMagicStuff.cs new file mode 100644 index 0000000..07bf003 --- /dev/null +++ b/Source/DMagicStuff.cs @@ -0,0 +1,63 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using DMagic.Part_Modules; + + +namespace ScienceAlert +{ + using Log = ReeperCommon.Log; + internal class DMagicStuff + { + static internal DMagicStuff fetch = null; + + private Type _tDMModuleScienceAnimate; + + internal DMagicStuff() + { + fetch = this; + _tDMModuleScienceAnimate = ReeperCommon.DMagicFactory.getType("DMagic.Part_Modules.DMModuleScienceAnimate"); + } + + + public bool inheritsFromOrIsDMModuleScienceAnimate(object o) + { + if (_tDMModuleScienceAnimate == null) + { + return false; + } + return ((o.GetType().IsSubclassOf(_tDMModuleScienceAnimate) || o.GetType() == _tDMModuleScienceAnimate)); + } + + + + internal bool RunExperiment(string sid, ModuleScienceExperiment exp, bool runSingleUse = true) + { + DMModuleScienceAnimate m = null; + + // If possible run with DMagic DMAPI + + IEnumerable lm2 = FlightGlobals.ActiveVessel.FindPartModulesImplementing().Where(x => inheritsFromOrIsDMModuleScienceAnimate(x) && x.experimentID == sid).ToList(); + + if (lm2.Any()) + { + m = lm2.FirstOrDefault(x => + { + return !x.Inoperable && + ((int)x.Fields.GetValue("experimentLimit") > 1 ? DMagic.DMAPI.experimentCanConduct(x) : DMagic.DMAPI.experimentCanConduct(x) && + (x.rerunnable || runSingleUse)); + }); + + if (m != null) + { + DMagic.DMAPI.deployDMExperiment(m, false); // maybe change this later + return true; + } + + } + + + return false; + } + } +} diff --git a/Source/GetAssemblyInfo.cs b/Source/GetAssemblyInfo.cs new file mode 100644 index 0000000..bcb3547 --- /dev/null +++ b/Source/GetAssemblyInfo.cs @@ -0,0 +1,158 @@ +using System; + +namespace ReeperCommon +{ + public static class GetAssemblyInfo + { + + public static System.Reflection.Assembly GetAssembly(string pAssemblyName) + { + System.Reflection.Assembly tMyAssembly = null; + + if (string.IsNullOrEmpty(pAssemblyName)) { return tMyAssembly; } + tMyAssembly = GetAssemblyEmbedded(pAssemblyName); + if (tMyAssembly == null) { GetAssemblyDLL(pAssemblyName); } + + return tMyAssembly; + }//System.Reflection.Assembly GetAssemblyEmbedded(string pAssemblyDisplayName) + + + public static System.Reflection.Assembly GetAssemblyEmbedded(string pAssemblyDisplayName) + { + System.Reflection.Assembly tMyAssembly = null; + + if (string.IsNullOrEmpty(pAssemblyDisplayName)) { return tMyAssembly; } + try //try #a + { + tMyAssembly = System.Reflection.Assembly.Load(pAssemblyDisplayName); + }// try #a + catch (Exception ex) + { + string m = ex.Message; + }// try #a + return tMyAssembly; + }//System.Reflection.Assembly GetAssemblyEmbedded(string pAssemblyDisplayName) + + + public static System.Reflection.Assembly GetAssemblyDLL(string pAssemblyNameDLL) + { + System.Reflection.Assembly tMyAssembly = null; + + if (string.IsNullOrEmpty(pAssemblyNameDLL)) { return tMyAssembly; } + try //try #a + { + if (!pAssemblyNameDLL.ToLower().EndsWith(".dll")) { pAssemblyNameDLL += ".dll"; } + tMyAssembly = System.Reflection.Assembly.LoadFrom(pAssemblyNameDLL); + }// try #a + catch (Exception ex) + { + string m = ex.Message; + }// try #a + return tMyAssembly; + }//System.Reflection.Assembly GetAssemblyFile(string pAssemblyNameDLL) + + + public static string GetVersionStringFromAssembly(string pAssemblyDisplayName) + { + string tVersion = "Unknown"; + System.Reflection.Assembly tMyAssembly = null; + + tMyAssembly = GetAssembly(pAssemblyDisplayName); + if (tMyAssembly == null) { return tVersion; } + tVersion = GetVersionString(tMyAssembly.GetName().Version.ToString()); + return tVersion; + }//string GetVersionStringFromAssemblyEmbedded(string pAssemblyDisplayName) + +#if false + public static string GetVersionString(Version pVersion) + { + string tVersion = "Unknown"; + if (pVersion == null) { return tVersion; } + tVersion = GetVersionString(pVersion.ToString()); + return tVersion; + }//string GetVersionString(Version pVersion) +#endif + + public static string GetVersionString(string pVersionString) + { + string tVersion = "Unknown"; + string[] aVersion; + + if (string.IsNullOrEmpty(pVersionString)) { return tVersion; } + aVersion = pVersionString.Split('.'); + if (aVersion.Length > 0) { tVersion = aVersion[0]; } + if (aVersion.Length > 1) { tVersion += "." + aVersion[1]; } + if (aVersion.Length > 2) { tVersion += "." + aVersion[2].PadLeft(4, '0'); } + if (aVersion.Length > 3) { tVersion += "." + aVersion[3].PadLeft(4, '0'); } + + return tVersion; + }//string GetVersionString(Version pVersion) + +#if false + public class VersionNumbers + { + public uint major = 0; + public uint minor = 0; + public uint patch = 0; + public uint build = 0; + } + + public static VersionNumbers GetVersionNumbers(string pVersionString) + { + string tVersion = "Unknown"; + string[] aVersion; + + VersionNumbers vn = new VersionNumbers(); + + if (string.IsNullOrEmpty(pVersionString)) { return vn; } + aVersion = pVersionString.Split('.'); + if (aVersion.Length > 0) + { + tVersion = aVersion[0]; + vn.major = uint.Parse(aVersion[0]); + } + if (aVersion.Length > 1) + { + tVersion += "." + aVersion[1]; + vn.minor = uint.Parse(aVersion[1]); + } + if (aVersion.Length > 2) + { + tVersion += "." + aVersion[2].PadLeft(4, '0'); + vn.patch = uint.Parse(aVersion[2]); + } + if (aVersion.Length > 3) + { + tVersion += "." + aVersion[3].PadLeft(4, '0'); + vn.build = uint.Parse(aVersion[3]); + } + + return vn; ; + }//string GetVersionNumbers(Version pVersion) + + public static string GetVersionStringFromAssemblyEmbedded(string pAssemblyDisplayName) + { + string tVersion = "Unknown"; + System.Reflection.Assembly tMyAssembly = null; + + tMyAssembly = GetAssemblyEmbedded(pAssemblyDisplayName); + if (tMyAssembly == null) { return tVersion; } + tVersion = GetVersionString(tMyAssembly.GetName().Version.ToString()); + return tVersion; + }//string GetVersionStringFromAssemblyEmbedded(string pAssemblyDisplayName) + + + public static string GetVersionStringFromAssemblyDLL(string pAssemblyDisplayName) + { + string tVersion = "Unknown"; + System.Reflection.Assembly tMyAssembly = null; + + tMyAssembly = GetAssemblyDLL(pAssemblyDisplayName); + if (tMyAssembly == null) { return tVersion; } + tVersion = GetVersionString(tMyAssembly.GetName().Version.ToString()); + return tVersion; + }//string GetVersionStringFromAssemblyEmbedded(string pAssemblyDisplayName) +#endif + + } +} \ No newline at end of file diff --git a/Source/InstallChecker.cs b/Source/InstallChecker.cs new file mode 100644 index 0000000..5322d6c --- /dev/null +++ b/Source/InstallChecker.cs @@ -0,0 +1,99 @@ +/** + * Based on the InstallChecker from the Kethane mod for Kerbal Space Program. + * https://github.com/Majiir/Kethane/blob/b93b1171ec42b4be6c44b257ad31c7efd7ea1702/Plugin/InstallChecker.cs + * + * Original is (C) Copyright Majiir. + * CC0 Public Domain (http://creativecommons.org/publicdomain/zero/1.0/) + * http://forum.kerbalspaceprogram.com/threads/65395-CompatibilityChecker-Discussion-Thread?p=899895&viewfull=1#post899895 + * + * This file has been modified extensively and is released under the same license. + */ +using System; +using System.IO; +using System.Linq; +using System.Reflection; +using UnityEngine; + +namespace ReeperCommon +{ + [KSPAddon(KSPAddon.Startup.Instantly, true)] + internal class Startup : MonoBehaviour + { + private void Start() + { + string v = "n/a"; + AssemblyTitleAttribute attributes = (AssemblyTitleAttribute)Attribute.GetCustomAttribute(Assembly.GetExecutingAssembly(), typeof(AssemblyTitleAttribute), false); + string title = attributes?.Title; + if (title == null) + { + title = "TitleNotAvailable"; + } + v = Assembly.GetExecutingAssembly().FullName; + if (v == null) + { + v = "VersionNotAvailable"; + } + Debug.Log("[" + title + "] Version " + v); + } + } + + [KSPAddon(KSPAddon.Startup.MainMenu, true)] + internal class InstallChecker : MonoBehaviour + { + private const string MODNAME = "Science Alert"; + private const string FOLDERNAME = "ScienceAlert"; + private const string EXPECTEDPATH = FOLDERNAME + "/Plugins"; + + protected void Start() + { + // Search for this mod's DLL existing in the wrong location. This will also detect duplicate copies because only one can be in the right place. + var assemblies = AssemblyLoader.loadedAssemblies.Where(a => a.assembly.GetName().Name == Assembly.GetExecutingAssembly().GetName().Name).Where(a => a.url != EXPECTEDPATH); + if (assemblies.Any()) + { + var badPaths = assemblies.Select(a => a.path).Select(p => Uri.UnescapeDataString(new Uri(Path.GetFullPath(KSPUtil.ApplicationRootPath)).MakeRelativeUri(new Uri(p)).ToString().Replace('/', Path.DirectorySeparatorChar))); + PopupDialog.SpawnPopupDialog + ( + new Vector2(0.5f, 0.5f), + new Vector2(0.5f, 0.5f), + "test", + "Incorrect " + MODNAME + " Installation", + MODNAME + " has been installed incorrectly and will not function properly. All files should be located in KSP/GameData/" + FOLDERNAME + ". Do not move any files from inside that folder.\n\nIncorrect path(s):\n" + String.Join("\n", badPaths.ToArray()), + "OK", + false, + HighLogic.UISkin + ); + Debug.Log("Incorrect " + MODNAME + " Installation: " + MODNAME + " has been installed incorrectly and will not function properly. All files should be located in KSP/GameData/" + EXPECTEDPATH + ". Do not move any files from inside that folder.\n\nIncorrect path(s):\n" + String.Join("\n", badPaths.ToArray()) + + ); + + } + + //// Check for Module Manager + //if (!AssemblyLoader.loadedAssemblies.Any(a => a.assembly.GetName().Name.StartsWith("ModuleManager") && a.url == "")) + //{ + // PopupDialog.SpawnPopupDialog("Missing Module Manager", + // modName + " requires the Module Manager mod in order to function properly.\n\nPlease download from http://forum.kerbalspaceprogram.com/threads/55219 and copy to the KSP/GameData/ directory.", + // "OK", false, HighLogic.Skin); + //} + + CleanupOldVersions(); + } + + /* + * Tries to fix the install if it was installed over the top of a previous version + */ + void CleanupOldVersions() + { + try + { + } + catch (Exception ex) + { + Debug.LogError("-ERROR- " + this.GetType().FullName + "[" + this.GetInstanceID().ToString("X") + "][" + Time.time.ToString("0.00") + "]: " + + "Exception caught while cleaning up old files.\n" + ex.Message + "\n" + ex.StackTrace); + + } + } + } +} + diff --git a/Properties/AssemblyInfo.cs b/Source/Properties/AssemblyInfo.cs similarity index 77% rename from Properties/AssemblyInfo.cs rename to Source/Properties/AssemblyInfo.cs index b33a5f2..66555b8 100644 --- a/Properties/AssemblyInfo.cs +++ b/Source/Properties/AssemblyInfo.cs @@ -1,14 +1,18 @@ -[assembly: System.Reflection.AssemblyVersion("1.9.0.1")] -[assembly: System.Diagnostics.Debuggable(System.Diagnostics.DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] -[assembly: System.Reflection.AssemblyCompany("OLDD")] -[assembly: System.Reflection.AssemblyConfiguration("")] -[assembly: System.Reflection.AssemblyCopyright("Copyright © 2018")] -[assembly: System.Reflection.AssemblyDescription("")] -[assembly: System.Reflection.AssemblyFileVersion("1.9.0.1")] -[assembly: System.Reflection.AssemblyProduct("ScienceAlert")] -[assembly: System.Reflection.AssemblyTitle("ScienceAlert")] -[assembly: System.Reflection.AssemblyTrademark("")] -[assembly: System.Runtime.CompilerServices.CompilationRelaxations(8)] -[assembly: System.Runtime.CompilerServices.RuntimeCompatibility(WrapNonExceptionThrows = true)] -[assembly: System.Runtime.InteropServices.ComVisible(false)] -[assembly: System.Runtime.InteropServices.Guid("de7585d5-53eb-408c-ba04-a7bcee8f51a4")] +//[assembly: System.Reflection.AssemblyVersion("1.9.0.1")] +[assembly: System.Diagnostics.Debuggable(System.Diagnostics.DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] +[assembly: System.Reflection.AssemblyCompany("OLDD")] +[assembly: System.Reflection.AssemblyConfiguration("")] +[assembly: System.Reflection.AssemblyCopyright("Copyright © 2018")] +[assembly: System.Reflection.AssemblyDescription("")] +//[assembly: System.Reflection.AssemblyFileVersion("1.9.0.1")] +[assembly: System.Reflection.AssemblyProduct("ScienceAlert")] +[assembly: System.Reflection.AssemblyTitle("ScienceAlert")] +[assembly: System.Reflection.AssemblyTrademark("")] +[assembly: System.Runtime.CompilerServices.CompilationRelaxations(8)] +[assembly: System.Runtime.CompilerServices.RuntimeCompatibility(WrapNonExceptionThrows = true)] +[assembly: System.Runtime.InteropServices.ComVisible(false)] +[assembly: System.Runtime.InteropServices.Guid("de7585d5-53eb-408c-ba04-a7bcee8f51a4")] + + +[assembly: KSPAssemblyDependency("ClickThroughBlocker", 1, 0)] +[assembly: KSPAssemblyDependency("ToolbarController", 1, 0)] \ No newline at end of file diff --git a/Source/Properties/Settings.Designer.cs b/Source/Properties/Settings.Designer.cs new file mode 100644 index 0000000..4f682a1 --- /dev/null +++ b/Source/Properties/Settings.Designer.cs @@ -0,0 +1,26 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Properties { + + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "16.8.1.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { + + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); + + public static Settings Default { + get { + return defaultInstance; + } + } + } +} diff --git a/Source/Properties/Settings.settings b/Source/Properties/Settings.settings new file mode 100644 index 0000000..049245f --- /dev/null +++ b/Source/Properties/Settings.settings @@ -0,0 +1,6 @@ + + + + + + diff --git a/Source/ReeperCommon/AudioPlayer.cs b/Source/ReeperCommon/AudioPlayer.cs new file mode 100644 index 0000000..b889ee3 --- /dev/null +++ b/Source/ReeperCommon/AudioPlayer.cs @@ -0,0 +1,140 @@ +using System.Collections.Generic; +using System.Linq; +using UnityEngine; + +namespace ReeperCommon +{ + internal class AudioPlayer : MonoBehaviour + { + private static AudioPlayer _instance; + private readonly Dictionary sounds = new Dictionary(); + private AudioSource source; + + public static AudioPlayer Audio + { + get + { + if (_instance != null) return _instance; + GameObject gameObject = new GameObject("Reeper.AudioPlayer", typeof(AudioSource)); + gameObject.AddComponent().SetSource(gameObject); + return _instance; + } + } + + public int Count => sounds.Count; + + private void Awake() + { + if (_instance == null) + { + _instance = this; + } + } + + private void OnDestroy() + { + if (_instance == this) + { + _instance = null; + } + } + + public void SetSource(GameObject src, bool b2d = true) + { + source = src.GetComponent() ?? src.AddComponent(); + } + + public int LoadSoundsFrom(string dir, bool b2D = true) + { + int counter = 0; + if (System.IO.Path.IsPathRooted(dir) && System.IO.Directory.Exists(dir)) + { + dir = System.IO.Path.GetFullPath(dir).Replace('\\', '/'); + dir = ConfigUtil.GetRelativeToGameData(dir); + } + else + { + dir = dir.TrimStart('\\', '/'); + if (!System.IO.Directory.Exists(System.IO.Path.Combine(System.IO.Path.GetFullPath(KSPUtil.ApplicationRootPath + "GameData"), dir))) + { + string text = System.IO.Path.Combine(ConfigUtil.GetDllDirectoryPath(), dir); + if (!System.IO.Directory.Exists(text)) + { + Log.Debug("[ScienceAlert]:AudioPlayer: Couldn't find '{0}'", dir); + return 0; + } + dir = ConfigUtil.GetRelativeToGameData(text).Replace('\\', '/'); + } + else + { + dir = dir.Replace('\\', '/'); + } + } + GameDatabase.Instance.databaseAudio.ForEach(delegate(AudioClip ac) + { + string text2 = ac.name; + int num = text2.LastIndexOf('/'); + if (num >= 0) + { + text2 = text2.Substring(0, num); + } + + if (!string.Equals(text2, dir)) return; + if (sounds.ContainsKey(ac.name))return; + sounds.Add(ac.name, new PlayableSound(ac)); + counter++; + }); + if (counter == 0) + { + Log.Warning("AudioPlayer: Didn't load any sounds from directory '{0}'", dir); + } + return counter; + } + + public bool PlayThenDelay(string name, float delay = 1f) + { + return Play(name, 1f, delay); + } + + public bool PlayUI(string name, float delay = 0f) + { + return Play(name, GameSettings.UI_VOLUME, delay); + } + + public bool Play(string name, float volume = 1f, float delay = 0f) + { + PlayableSound playableSound = null; + if (sounds.ContainsKey(name)) + { + playableSound = sounds[name]; + } + else + { + string text = sounds.Keys.ToList().SingleOrDefault((string k) => string.Equals(PlayableSound.GetShortName(k), name)); + if (!string.IsNullOrEmpty(text) && sounds.ContainsKey(text)) + { + playableSound = sounds[text]; + } + } + if (playableSound == null) return false; + + if (!(Time.realtimeSinceStartup - playableSound.nextPlayableTime > 0f)) return false; + + if (source == null) + { + SetSource(gameObject); + } + try + { + source.PlayOneShot(playableSound.clip, Mathf.Clamp(volume, 0f, 1f)); + playableSound.nextPlayableTime = Time.realtimeSinceStartup + delay; + bool result = true; + return result; + } + catch (System.Exception) + { + return false; + } + } + } +} diff --git a/Source/ReeperCommon/ConfigNodeTypeHandler.cs b/Source/ReeperCommon/ConfigNodeTypeHandler.cs new file mode 100644 index 0000000..ee1b74b --- /dev/null +++ b/Source/ReeperCommon/ConfigNodeTypeHandler.cs @@ -0,0 +1,127 @@ +using UnityEngine; + +namespace ReeperCommon +{ + [System.AttributeUsage(System.AttributeTargets.Property | System.AttributeTargets.Field | System.AttributeTargets.Event)] + internal class DoNotSerialize : System.Attribute + {} + + internal interface IConfigNodeTypeFormatter + { + string Serialize(object obj); + + object Deserialize(object obj, string value); + } + + internal interface IReeperSerializable + { + void OnSerialize(ConfigNode node); + + void OnDeserialize(ConfigNode node); + } + + internal class ConfigNodeTypeHandler + { + internal class Vector2Formatter : IConfigNodeTypeFormatter + { + public string Serialize(object obj) + { + return KSPUtil.WriteVector((Vector2)obj); + } + + public object Deserialize(object obj, string value) + { + Vector2 vector = (Vector2)obj; + vector = KSPUtil.ParseVector2(value); + return vector; + } + } + + private System.Collections.Generic.Dictionary handlers = new System.Collections.Generic.Dictionary(); + + internal ConfigNodeTypeHandler() + { + AddFormatter(typeof(Vector2), typeof(Vector2Formatter)); + } + + internal void AddFormatter(System.Type targetType, IConfigNodeTypeFormatter impl) + { + if (handlers.ContainsKey(targetType)) + { + handlers[targetType] = impl; + return; + } + handlers.Add(targetType, impl); + } + + internal void AddFormatter(System.Type targetType, System.Type formatter) + { + try + { + if (!typeof(IConfigNodeTypeFormatter).IsAssignableFrom(formatter)) return; + IConfigNodeTypeFormatter value = (IConfigNodeTypeFormatter)System.Activator.CreateInstance(formatter); + if (handlers.ContainsKey(targetType)) + handlers[targetType] = value; + else + handlers.Add(targetType, value); + } + catch (System.Exception ex) + { + Log.Debug("[ScienceAlert]:ConfigNodeTypeHandler.AddFormatter: Exception while attempting to add handler for type '{0}' (of type {1}): {2}", targetType.FullName, formatter.FullName, ex); + } + } + + internal string Serialize(ref T obj) + { + System.Type typeFromHandle = typeof(T); + if (handlers.ContainsKey(typeFromHandle)) + { + IConfigNodeTypeFormatter configNodeTypeFormatter = handlers[typeFromHandle]; + return configNodeTypeFormatter.Serialize(obj); + } + if (typeFromHandle.IsEnum) + { + return obj.ToString(); + } + return obj.ToString(); + } + + internal bool Deserialize(ref T obj, string value) + { + if (!handlers.ContainsKey(typeof(T))) + { + bool result; + if (typeof(T).IsEnum) + { + try + { + obj = (T)System.Enum.Parse(typeof(T), value, true); + return true; + } + catch (System.Exception) + { + return false; + } + } + try + { + obj = ConfigUtil.ParseThrowable(value); + result = true; + } + catch (System.Exception) + { + result = false; + } + return result; + } + + object obj2 = handlers[typeof(T)].Deserialize(obj, value); + if (obj2 != null) + { + obj = (T)obj2; + return true; + } + return false; + } + } +} diff --git a/Source/ReeperCommon/ConfigUtil.cs b/Source/ReeperCommon/ConfigUtil.cs new file mode 100644 index 0000000..9410e3f --- /dev/null +++ b/Source/ReeperCommon/ConfigUtil.cs @@ -0,0 +1,215 @@ +using System; +using System.Reflection; +using UnityEngine; + +namespace ReeperCommon +{ + public static class ConfigUtil + { + public static T ParseEnum(this ConfigNode node, string valueName, T defaultValue) + { + try + { + string value = node.GetValue(valueName); + T result; + if (string.IsNullOrEmpty(value)) + { + result = defaultValue; + return result; + } + Enum.GetValues(typeof(T)); + result = (T)Enum.Parse(typeof(T), value, true); + return result; + } + catch (Exception ex) + { + Log.Debug("[ScienceAlert]:Settings: Failed to parse value '{0}' from ConfigNode, resulted in an exception {1}", valueName, ex); + } + return defaultValue; + } + + public static string Parse(this ConfigNode node, string valueName, string defaultValue = "") + { + try + { + string result; + if (!node.HasValue(valueName)) + { + result = defaultValue; + return result; + } + result = node.GetValue(valueName); + return result; + } + catch (Exception ex) + { + Log.Debug("[ScienceAlert]:Settings: Failed to parse string value '{0}' from ConfigNode, resulted in an exception {1}", valueName, ex); + } + return defaultValue; + } + + public static T Parse(string value) + { + return Parse(value, default(T)); + } + + public static T Parse(string value, T defaultValue) + { + try + { + MethodInfo method = typeof(T).GetMethod("TryParse", new[] + { + typeof(string), + typeof(T).MakeByRefType() + }); + if (method == null) + { + Log.Debug("[ScienceAlert]:Failed to locate TryParse in {0}", typeof(T).FullName); + } + else + { + object[] array = { + value, + default(T) + }; + T result; + if ((bool)method.Invoke(null, array)) + { + result = (T)array[1]; + return result; + } + result = defaultValue; + return result; + } + } + catch (Exception) + { + T result = defaultValue; + return result; + } + return defaultValue; + } + + public static T ParseThrowable(string value) + { + T result; + try + { + MethodInfo method = typeof(T).GetMethod("TryParse", new[] + { + typeof(string), + typeof(T).MakeByRefType() + }); + if (method == null) + { + throw new Exception("TryParse method not found"); + } + object[] array = { + value, + default(T) + }; + if (!(bool)method.Invoke(null, array)) + { + throw new Exception("TryParse invoke reports failure"); + } + result = (T)array[1]; + } + catch (Exception ex) + { + Log.Debug("[ScienceAlert]:ConfigUtil.Parse<{0}>: Failed to parse from value '{1}': {2}", typeof(T).FullName, value, ex); + throw; + } + return result; + } + + public static T Parse(this ConfigNode node, string valueName, T defaultValue) + { + try + { + T result; + if (!node.HasValue(valueName)) + { + result = defaultValue; + return result; + } + string value = node.GetValue(valueName); + result = Parse(value, defaultValue); + return result; + } + catch (Exception ex) + { + Log.Debug("[ScienceAlert]:ConfigUtil.Parse<{0}>: Exception while parsing a value named {1}: {2}", typeof(T).FullName, valueName, ex); + } + return defaultValue; + } + + public static string ReadString(this ConfigNode node, string valueName, string defaultValue = "") + { + if (node == null || !node.HasValue(valueName)) + { + return defaultValue; + } + return node.GetValue(valueName); + } + + public static void Set(this ConfigNode node, string valueName, string value) + { + if (node.HasValue(valueName)) + { + node.SetValue(valueName, value); + return; + } + node.AddValue(valueName, value); + } + + public static void Set(this ConfigNode node, string valueName, T value) + { + node.Set(valueName, value.ToString()); + } + + public static string GetDllDirectoryPath() + { + return System.IO.Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); + } + + public static string GetRelativeToGameData(string path) + { + if (!path.Contains("GameData")) + { + Log.Debug( + $"GetRelativeToGameData: Given path '{path}' does not reside in GameData. The plugin does not appear to be installed correctly."); + throw new FormatException($"GetRelativeToGameData: path '{path}' does not contain 'GameData'"); + } + int num = path.IndexOf("GameData"); + string text = ""; + if (path.Length > num + "GameData".Length + 1) + { + text = path.Substring(num + "GameData".Length + 1); + } + return text; + } + + public static Rect ReadRect(this ConfigNode node, string name, Rect defaultValue = default(Rect)) + { + if (node.HasValue(name)) + { + try + { + Vector4 vector = KSPUtil.ParseVector4(node.GetValue(name)); + return new Rect(vector.x, vector.y, vector.z, vector.w); + } + catch (Exception ex) + { + Log.Debug("[ScienceAlert]:ConfigUtil.ReadRect: exception while reading value '{0}': {1}", name, ex); + } + return defaultValue; + } + return defaultValue; + } + + public static Vector4 AsVector(this Rect rect) + { + return new Vector4(rect.x, rect.y, rect.width, rect.height); + } + } +} diff --git a/Source/ReeperCommon/DMagic.cs b/Source/ReeperCommon/DMagic.cs new file mode 100644 index 0000000..dec6fb7 --- /dev/null +++ b/Source/ReeperCommon/DMagic.cs @@ -0,0 +1,121 @@ +// +// This file copied from [x]science +// +// Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International Public License + +using System; + +namespace ReeperCommon +{ + /// + /// Class to access the DMagic API via reflection so we don't have to recompile when the DMagic mod updates. If the DMagic API changes, we will need to modify this code. + /// + + public class DMagicFactory + { + private static bool _dmagicIsInstalled = false; + private static bool _dmagicSciAnimateGenIsInstalled = false; + + public static bool DMagic_IsInstalled { get { return _dmagicIsInstalled; } } + public static bool DMagicScienceAnimateGeneric_IsInstalled { get { return _dmagicSciAnimateGenIsInstalled; } } + + + static internal ScienceAlert.DMagicStuff DMStuff { get; private set; } + static internal ScienceAlert.DMagic_SciAnimGenFactory DMM_SciAnimGenericStuff { get; private set; } + + + static public void InitDMagicFactory() + { + _dmagicIsInstalled = false; + + if (HasMod("DMagic")) + { + _dmagicIsInstalled = true; + doit_DMStuff(); + } + if (HasMod("DMModuleScienceAnimateGeneric")) + { + _dmagicSciAnimateGenIsInstalled = true; + doit_DMSciAnimGenStuff(); + } + + } + + static void doit_DMStuff() + { + DMStuff = new ScienceAlert.DMagicStuff(); + } + private static bool HasMod(string modIdent) + { + foreach (AssemblyLoader.LoadedAssembly a in AssemblyLoader.loadedAssemblies) + { + if (modIdent == a.name) + return true; + } + return false; + } + + internal static bool RunExperiment(string sid, ModuleScienceExperiment exp, bool runSingleUse = true) + { + return ScienceAlert.DMagicStuff.fetch.RunExperiment(sid, exp, runSingleUse); + } + static void doit_DMSciAnimGenStuff() + { + DMM_SciAnimGenericStuff = new ScienceAlert.DMagic_SciAnimGenFactory(); + if (DMM_SciAnimGenericStuff != null) + { + string ver = GetAssemblyInfo.GetVersionStringFromAssembly("DMModuleScienceAnimateGeneric"); + if (String.Compare(ver, "0.23") < 0) + { + Log.Error("Old version of DMModuleScienceAnimateGeneric installed, disabling any references to that"); + DMM_SciAnimGenericStuff = null; + _dmagicSciAnimateGenIsInstalled = false; + } + else + Log.Info("DMModuleScienceAnimateGeneric version: " + GetAssemblyInfo.GetVersionStringFromAssembly("DMModuleScienceAnimateGeneric")); + } + + } + + + internal static bool RunSciAnimGenExperiment(string sid, ModuleScienceExperiment exp, bool runSingleUse = true) + { + return ScienceAlert.DMagic_SciAnimGenFactory.fetch.RunExperiment(sid, exp, runSingleUse); + } + + internal static Type getType(string name) + { + Type type = null; +#if false + foreach (var s in AssemblyLoader.loadedAssemblies) + { + foreach (var s2 in s.assembly.GetTypes()) + { + if (s2.FullName == name) + + { + type = s2; + return type; + } + } + } + return null; + // + // The following is the original code, but was replaced because it was generating exceptions when + // the DMagic was loaded and the DMModuleScienceAnimationGeneric was not + // +#else + AssemblyLoader.loadedAssemblies.TypeOperation(t => + { + if (t.FullName != null && t.FullName == name) + { + type = t; + } + }); + return type; +#endif + } + + + } +} \ No newline at end of file diff --git a/Source/ReeperCommon/DraggableWindow.cs b/Source/ReeperCommon/DraggableWindow.cs new file mode 100644 index 0000000..07a6541 --- /dev/null +++ b/Source/ReeperCommon/DraggableWindow.cs @@ -0,0 +1,323 @@ +using UnityEngine; +using ClickThroughFix; + +namespace ReeperCommon +{ + public delegate void WindowClosedDelegate(); + public delegate void WindowDelegate(bool tf); + + public abstract class DraggableWindow : MonoBehaviour + { + protected Rect windowRect; // = default(Rect); + protected Rect lastRect; // = default(Rect); + /* static */ private GUISkin skin = null; + private int winId; // = Random.Range(2444, 2147483647); + private static Vector2 offset = new Vector2(4f, 4f); + private static GUIStyle buttonStyle; + private bool draggable = true; + private bool visible = true; + private static Texture2D hoverBackground; + private static GUISkin defaultSkin; + public event WindowDelegate OnVisibilityChange = delegate { }; + + public event WindowDelegate OnDraggabilityChange = delegate { }; + + public event WindowClosedDelegate OnClosed = delegate { }; + + public bool Draggable + { + get + { + return draggable; + } + protected set + { + if (draggable != value) + { + OnDraggabilityChange(value); + } + draggable = value; + } + } + + public bool ShrinkHeightToFit + { + get; + set; + } + + public bool Visible + { + get + { + return visible; + } + set + { + if (value != visible) + { + OnVisibilityChange(value); + } + visible = value; + + if (gameObject.activeInHierarchy != visible && !visible) + { + OnClosed(); + } + gameObject.SetActive(visible); + } + } + + public int WindowID + { + get + { + return winId; + } + private set + { + winId = value; + } + } + + public string Title + { + get; + set; + } + + public GUISkin Skin + { + get + { + return skin ?? DefaultSkin; + } + set + { + skin = value ?? DefaultSkin; + } + } +#if false + public Rect WindowRect + { + get + { + return lastRect; + } + } +#endif + public bool ClampToScreen + { + get; + set; + } + + public static Texture2D LockTexture + { + get; + set; + } + + public static Texture2D UnlockTexture + { + get; + set; + } + + public static Texture2D CloseTexture + { + get; + set; + } + + public static Texture2D ButtonHoverBackground + { + get + { + return hoverBackground ?? ResourceUtil.GenerateRandom(16, 16); + } + set + { + hoverBackground = value; + if (buttonStyle != null) + { + buttonStyle.hover.background = value; + } + } + } + + public static string ButtonSound + { + get; + set; + } + + public static GUISkin DefaultSkin + { + get + { + return defaultSkin ?? HighLogic.Skin; + } + set + { + defaultSkin = value; + } + } + + protected void Awake() + { + winId = Random.Range(2444, 2147483647); + if (buttonStyle == null) + { + buttonStyle = new GUIStyle(GUIStyle.none); + if (hoverBackground != null) + { + buttonStyle.hover.background = hoverBackground; + } + } + Draggable = true; + Visible = true; + ClampToScreen = true; + Title = "Draggable Window"; + windowRect = Setup(); + lastRect = new Rect(windowRect); + + //windowRect = default(Rect); + //lastRect = default(Rect); + + GameEvents.onHideUI.Add(OnHideUI); + GameEvents.onShowUI.Add(OnShowUI); + } + + private void Start() + { + Log.Debug("ALERT:DraggableWindow {0} Start", Title); + } + + protected virtual void OnDestroy() + { + Log.Debug("ALERT:DraggableWindow.OnDestroy"); + GameEvents.onHideUI.Remove(OnHideUI); + GameEvents.onShowUI.Remove(OnShowUI); + } + + protected void OnEnable() + { + OnVisibilityChange(true); + } + + protected void OnDisable() + { + OnVisibilityChange(false); + } + + public void Show(bool tf) + { + Visible = tf; + } + + protected void Update() + { + if (ShrinkHeightToFit) + { + windowRect.height = 1f; + } + } + + protected void OnGUI() + { + GUI.skin = Skin; + windowRect = ClickThruBlocker.GUILayoutWindow(winId, windowRect, _InternalDraw, Title); + if (ClampToScreen) + windowRect = KSPUtil.ClampRectToScreen(windowRect); + } + + private void _InternalDraw(int winid) + { + DrawUI(); +#if true + lastRect.x = windowRect.x; + lastRect.y = windowRect.y; + GUILayout.BeginArea(new Rect(0f, offset.y, lastRect.width, lastRect.height)); + lastRect = new Rect(windowRect); + GUILayout.BeginHorizontal(GUILayout.ExpandWidth(true), GUILayout.ExpandHeight(false)); + GUILayout.FlexibleSpace(); + if (LockTexture != null && UnlockTexture != null) + { + if (GUILayout.Button(Draggable ? UnlockTexture : LockTexture, buttonStyle)) + { + Draggable = !Draggable; + if (!string.IsNullOrEmpty(ButtonSound)) + { + AudioPlayer.Audio.PlayUI(ButtonSound); + } + Log.Debug("ALERT:DraggableWindow {0}", Draggable ? "unlocked" : "locked"); + } + if (CloseTexture != null) + { + GUILayout.Space(offset.x * 0.5f); + } + } + if (CloseTexture != null && GUILayout.Button(CloseTexture, buttonStyle)) + { + if (!string.IsNullOrEmpty(ButtonSound)) + { + AudioPlayer.Audio.PlayUI(ButtonSound); + } + OnCloseClick(); + } + GUILayout.Space(offset.x); + GUILayout.EndHorizontal(); + GUILayout.EndArea(); + #endif + if (Draggable) + { + GUI.DragWindow(); + } + } + + protected abstract Rect Setup(); + + protected abstract void DrawUI(); + + protected abstract void OnCloseClick(); + + private void OnHideUI() + { + gameObject.SetActive(false); + } + + private void OnShowUI() + { + gameObject.SetActive(Visible); + } + + public void SaveInto(ConfigNode node) + { + if (node != null) + { + node.Set("WindowX", windowRect.x); + node.Set("WindowY", windowRect.y); + node.Set("Draggable", Draggable); + node.Set("Visible", Visible); + Log.Debug("ALERT:DraggableWindow.SaveInto: Saved window {0} as ConfigNode {1}", Title, node.ToString()); + return; + } + Log.Warning("GuiUtil.DraggableWindow: Can't save into null ConfigNode"); + } + + public bool LoadFrom(ConfigNode node) + { + if (node != null) + { + windowRect.x = node.Parse("WindowX", (float)Screen.width * 0.5f - windowRect.width * 0.5f); + windowRect.y = node.Parse("WindowY", (float)Screen.height * 0.5f - windowRect.height * 0.5f); + Draggable = node.Parse("Draggable", true); + Visible = node.Parse("Visible", false); + return node.HasValue("WindowX") && node.HasValue("WindowY"); + } + Log.Warning("GuiUtil.DraggableWindow: Can't load from null ConfigNode"); + return false; + } + } +} diff --git a/Source/ReeperCommon/Log.cs b/Source/ReeperCommon/Log.cs new file mode 100644 index 0000000..0ad03da --- /dev/null +++ b/Source/ReeperCommon/Log.cs @@ -0,0 +1,214 @@ +using System; +using System.Collections; +using System.Diagnostics; + +namespace ReeperCommon +{ + public static class Log + { + public enum LEVEL + { + OFF = 0, + ERROR = 1, + WARNING = 2, + INFO = 3, + DETAIL = 4, + TRACE = 5, + PERFORMANCE = 6 + }; + + public static LEVEL level = LEVEL.INFO; + + private static readonly String PREFIX = "ScienceAlert" + ": "; + + public static LEVEL GetLevel() + { + return level; + } + + public static void SetLevel(LEVEL level) + { + UnityEngine.Debug.Log("log level " + level); + Log.level = level; + } + + public static LEVEL GetLogLevel() + { + return level; + } + + private static bool IsLevel(LEVEL level) + { + return level == Log.level; + } + + public static bool IsLogable(LEVEL level) + { + return level <= Log.level; + } + + public static void Trace(String msg) + { + if (IsLogable(LEVEL.TRACE)) + { + UnityEngine.Debug.Log(PREFIX + msg); + } + } + + public static void Detail(String msg) + { + if (IsLogable(LEVEL.DETAIL)) + { + UnityEngine.Debug.Log(PREFIX + msg); + } + } + + [ConditionalAttribute("DEBUG")] + public static void Info(String msg) + { + if (IsLogable(LEVEL.INFO)) + { + UnityEngine.Debug.Log(PREFIX + msg); + } + } + + [ConditionalAttribute("DEBUG")] + public static void Test(String msg) + { + //if (IsLogable(LEVEL.INFO)) + { + UnityEngine.Debug.LogWarning(PREFIX + "TEST:" + msg); + } + } + + public static void Warning(String msg) + { + if (IsLogable(LEVEL.WARNING)) + { + UnityEngine.Debug.LogWarning(PREFIX + msg); + } + } + + public static void Error(String msg) + { + if (IsLogable(LEVEL.ERROR)) + { + UnityEngine.Debug.LogError(PREFIX + msg); + } + } + + public static void Exception(Exception e) + { + Log.Error("exception caught: " + e.GetType() + ": " + e.Message); + } + + + internal static void Write(string message, LEVEL level) + { + + switch (level) + { + case LEVEL.ERROR: + Error(message); + return; + case LEVEL.DETAIL: + Detail(message); + return; + case LEVEL.WARNING: + Warning(message); + return; + case LEVEL.INFO: + Info(message); + return; + case LEVEL.PERFORMANCE: + UnityEngine.Debug.Log("[PERF] " + message); + return; + } + UnityEngine.Debug.Log(message); + } + + + internal static void Write(string message, LEVEL level, params object[] strParams) + { + + Write(string.Format(message, strParams), level); + + } + [ConditionalAttribute("DEBUG")] + internal static void Debug(string message, params object[] strParams) + { + Write(message, LEVEL.INFO, strParams); + } + + internal static void Normal(string message, params object[] strParams) + { + Write(message, LEVEL.INFO, strParams); + } + + internal static void Warning(string message, params object[] strParams) + { + Write(message, LEVEL.WARNING, strParams); + } + + internal static void Error(string message, params object[] strParams) + { + Write(message, LEVEL.ERROR, strParams); + } + +#if OLDLOG + internal static void SaveInto(ConfigNode parentNode) + { + ConfigNode configNode = parentNode.AddNode(new ConfigNode("LogSettings")); + configNode.AddValue("LogMask", (int)Level); + string[] names = System.Enum.GetNames(typeof(LogMask)); + System.Array values = System.Enum.GetValues(typeof(LogMask)); + configNode.AddValue("// Bit index", "message type"); + for (int i = 0; i < names.Length - 1; i++) + { + configNode.AddValue($"// Bit {i}", values.GetValue(i)); + } + Debug("[ScienceAlert].SaveInto = {0}", configNode.ToString()); + } + + internal static void LoadFrom(ConfigNode parentNode) + { + if (parentNode == null || !parentNode.HasNode("LogSettings")) + { + Warning("[ScienceAlert] failed, did not find LogSettings in: {0}", parentNode != null ? parentNode.ToString() : ""); + return; + } + ConfigNode node = parentNode.GetNode("LogSettings"); + try + { + if (!node.HasValue("LogMask")) + { + throw new System.Exception("[ScienceAlert]:No LogMask value in ConfigNode"); + } + string value = node.GetValue("LogMask"); + int num = 0; + if (int.TryParse(value, out num)) + { + if (num == 0) + { + Warning("[ScienceAlert]: Log disabled"); + } + Level = (LogMask)num; + Debug("[ScienceAlert]:Loaded LogMask = {0} from ConfigNode", Level.ToString()); + } + else + { + Debug("[ScienceAlert]: LogMask value '{0}' cannot be converted to LogMask", value); + } + } + catch (System.Exception ex) + { + Warning("[ScienceAlert] failed with exception: {0}", ex); + } + } + } +#endif + + + + } +} diff --git a/Source/ReeperCommon/PlayableSound.cs b/Source/ReeperCommon/PlayableSound.cs new file mode 100644 index 0000000..f55c9a7 --- /dev/null +++ b/Source/ReeperCommon/PlayableSound.cs @@ -0,0 +1,33 @@ +using UnityEngine; + +namespace ReeperCommon +{ + internal class PlayableSound + { + public AudioClip clip; + + public string shortName = ""; + + public float nextPlayableTime; + + internal PlayableSound(AudioClip aclip) + { + clip = aclip; + nextPlayableTime = 0f; + shortName = GetShortName(aclip.name); + } + + public static string GetShortName(string name) + { + if (name.Contains("/")) + { + int num = name.LastIndexOf('/'); + if (num >= 0) + { + return name.Substring(num + 1); + } + } + return name; + } + } +} diff --git a/Source/ReeperCommon/ReeperConfigNodeExtensions.cs b/Source/ReeperCommon/ReeperConfigNodeExtensions.cs new file mode 100644 index 0000000..1d88de0 --- /dev/null +++ b/Source/ReeperCommon/ReeperConfigNodeExtensions.cs @@ -0,0 +1,255 @@ +using System; +using System.Linq; +using System.Reflection; +using System.Runtime.CompilerServices; + +namespace ReeperCommon +{ + [System.AttributeUsage(System.AttributeTargets.Property | System.AttributeTargets.Field)] + internal class Subsection : System.Attribute + { + private string sectionName = "Subsection"; + + public string Section => sectionName; + + public Subsection(string name) + { + sectionName = name; + if (string.IsNullOrEmpty(name)) + { + sectionName = "Subsection"; + } + } + } + + public static class ReeperConfigNodeExtensions + { + internal static ConfigNode CreateConfigFromObjectEx(this object obj, ConfigNodeTypeHandler typeFormatter = null) + { + ConfigNode result; + try + { + ConfigNode configNode = new ConfigNode(obj.GetType().Name); + typeFormatter = typeFormatter ?? new ConfigNodeTypeHandler(); + FieldInfo[] objectFields = GetObjectFields(obj); + FieldInfo[] array = objectFields; + for (int i = 0; i < array.Length; i++) + { + FieldInfo fieldInfo = array[i]; + object[] customAttributes = fieldInfo.GetCustomAttributes(false); + object value = fieldInfo.GetValue(obj); + if (value != null) + { + if (typeof(ConfigNode).IsAssignableFrom(fieldInfo.FieldType)) + { + ConfigNode configNode2 = new ConfigNode(fieldInfo.Name); + ConfigNode configNode3 = ((ConfigNode)Convert.ChangeType(value, typeof(ConfigNode))).CreateCopy(); + if (string.IsNullOrEmpty(configNode3.name)) + configNode3.name = "ConfigNode"; + configNode2.ClearData(); + Subsection subsection = customAttributes.SingleOrDefault(attr => attr is Subsection) as Subsection; + if (subsection == null) + configNode2.AddNode(configNode3); + else + configNode2.AddNode(subsection.Section).AddNode(configNode3); + configNode.AddNode(configNode2); + } + else + { + MethodInfo method = typeFormatter.GetType().GetMethod("Serialize", BindingFlags.Instance | BindingFlags.NonPublic); + if (method == null) + { + Log.Debug("[ScienceAlert]:CreateConfigFromObjectEx: Serialize method not found"); + } + MethodInfo methodInfo = method.MakeGenericMethod(fieldInfo.FieldType); + string value2 = methodInfo.Invoke(typeFormatter, new[] + { + value + }) as string; + if (string.IsNullOrEmpty(value2)) + { + Log.Warning("ConfigUtil.CreateConfigFromObjectEx: null or empty return value for serialized type {0}", fieldInfo.FieldType.Name); + } + WriteValue(configNode, fieldInfo.Name, value2, customAttributes); + } + } + else + { + Log.Warning("Could not get value for " + fieldInfo.Name); + } + } + PropertyInfo[] objectProperties = GetObjectProperties(obj); + PropertyInfo[] array2 = objectProperties; + for (int j = 0; j < array2.Length; j++) + { + PropertyInfo propertyInfo = array2[j]; + object obj2 = propertyInfo.GetGetMethod(true).Invoke(obj, null); + object[] customAttributes2 = propertyInfo.GetCustomAttributes(true); + MethodInfo method2 = typeFormatter.GetType().GetMethod("Serialize", BindingFlags.Instance | BindingFlags.NonPublic); + if (method2 == null) + { + Log.Debug("[ScienceAlert]:CreateConfigFromObjectEx: Serialize method not found"); + } + else + { + MethodInfo methodInfo2 = method2.MakeGenericMethod(propertyInfo.PropertyType); + string value3 = methodInfo2.Invoke(typeFormatter, new[] + { + obj2 + }) as string; + if (string.IsNullOrEmpty(value3)) + { + Log.Warning("ConfigUtil.CreateConfigFromObjectEx: null or empty return value for serialized type {0}", propertyInfo.PropertyType.Name); + } + WriteValue(configNode, propertyInfo.Name, value3, customAttributes2); + } + } + if (obj is IReeperSerializable) + { + ((IReeperSerializable)obj).OnSerialize(configNode); + } + result = configNode; + } + catch (Exception ex) + { + Log.Debug("[ScienceAlert]:ConfigUtil.CreateConfigFromObjectEx: Exception {0}", ex); + result = null; + } + return result; + } + + internal static bool CreateObjectFromConfigEx(this ConfigNode node, object obj, ConfigNodeTypeHandler typeFormatter = null) + { + bool flag = true; + typeFormatter = typeFormatter ?? new ConfigNodeTypeHandler(); + FieldInfo[] objectFields = GetObjectFields(obj); + PropertyInfo[] objectProperties = GetObjectProperties(obj); + Log.Debug("ALERT:CreateObjectFromConfig: Found {0} fields and {1} properties", objectFields.Length, objectProperties.Length); + FieldInfo[] array = objectFields; + for (int i = 0; i < array.Length; i++) + { + FieldInfo fieldInfo = array[i]; + try + { + object[] customAttributes = fieldInfo.GetCustomAttributes(true); + if (typeof(ConfigNode).IsAssignableFrom(fieldInfo.FieldType)) + { + if (node.HasNode(fieldInfo.Name)) + { + Convert.ChangeType(fieldInfo.GetValue(obj) ?? new ConfigNode(), typeof(ConfigNode)); + ConfigNode node2 = node.GetNode(fieldInfo.Name); + Subsection subsection = customAttributes.SingleOrDefault(attr => attr is Subsection) as Subsection; + if (subsection != null) + { + if (node2.HasNode(subsection.Section)) + { + node2 = node2.GetNode(subsection.Section); + } + } + if (node2.CountNodes == 1) + { + ConfigNode value = node2.nodes[0]; + fieldInfo.SetValue(obj, value); + } + } + } + else + { + string text = ReadValue(node, fieldInfo.Name, fieldInfo.GetCustomAttributes(true)); + if (!string.IsNullOrEmpty(text)) + { + MethodInfo method = typeFormatter.GetType().GetMethod("Deserialize", BindingFlags.Instance | BindingFlags.NonPublic); + MethodInfo methodInfo = method.MakeGenericMethod(fieldInfo.FieldType); + if (!(bool)methodInfo.Invoke(typeFormatter, new[] + { + fieldInfo.GetValue(obj), + text + })) + { + flag = false; + } + } + } + } + catch (Exception ex) + { + Log.Debug("[ScienceAlert]:Exception while deserializing field '{0}': {1}", fieldInfo.Name, ex); + flag = false; + } + } + PropertyInfo[] array2 = objectProperties; + for (int j = 0; j < array2.Length; j++) + { + PropertyInfo propertyInfo = array2[j]; + try + { + string text2 = ReadValue(node, propertyInfo.Name, propertyInfo.GetCustomAttributes(true)); + if (!string.IsNullOrEmpty(text2)) + { + MethodInfo method2 = typeFormatter.GetType().GetMethod("Deserialize", BindingFlags.Instance | BindingFlags.NonPublic); + MethodInfo methodInfo2 = method2.MakeGenericMethod(propertyInfo.PropertyType); + object obj2 = Convert.ChangeType(propertyInfo.GetGetMethod(true).Invoke(obj, null), propertyInfo.PropertyType); + object[] array3 = { obj2, text2 }; + if (!(bool)methodInfo2.Invoke(typeFormatter, array3)) + flag = false; + else + propertyInfo.SetValue(obj, array3[0], BindingFlags.Instance | BindingFlags.SetProperty, null, null, null); + } + } + catch (Exception ex2) + { + Log.Debug("[ScienceAlert]:Exception while deserializing property '{0}': {1}", propertyInfo.Name, ex2); + flag = false; + } + } + if (obj is IReeperSerializable) + ((IReeperSerializable)obj).OnDeserialize(node); + return flag && objectFields.Count() > 0 || obj is IReeperSerializable; + } + + private static FieldInfo[] GetObjectFields(object obj) + { + return (from fi in obj.GetType().GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy) + where !fi.GetCustomAttributes(false).Any(attr => attr is CompilerGeneratedAttribute || attr is NonSerializedAttribute || attr is DoNotSerialize) + select fi).ToArray(); + } + + private static PropertyInfo[] GetObjectProperties(object obj) + { + return obj.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy).Where(delegate(PropertyInfo pi) + { + if (pi.GetGetMethod(true) != null && pi.GetSetMethod(true) != null) + return !pi.GetCustomAttributes(true).Any(attr => attr is DoNotSerialize || attr is NonSerializedAttribute); + return false; + }).ToArray(); + } + + private static void WriteValue(ConfigNode node, string valueName, string value, object[] attrs) + { + if (attrs == null) attrs = new object[0]; + Subsection subsection = attrs.SingleOrDefault(attr => attr is Subsection) as Subsection; + if (subsection != null) + { + if (node.HasNode(subsection.Section)) + node = node.GetNode(subsection.Section); + else + node = node.AddNode(subsection.Section); + } + attrs.ToList().ForEach(delegate{}); + node.AddValue(valueName, value); + } + + private static string ReadValue(ConfigNode node, string valueName, object[] attrs) + { + if (attrs == null) + attrs = new object[0]; + Subsection subsection = attrs.SingleOrDefault(attr => attr is Subsection) as Subsection; + if (subsection != null) + { + if (node.HasNode(subsection.Section)) + node = node.GetNode(subsection.Section); + } + return node.ReadString(valueName); + } + } +} diff --git a/Source/ReeperCommon/ResourceUtil.cs b/Source/ReeperCommon/ResourceUtil.cs new file mode 100644 index 0000000..593e7bb --- /dev/null +++ b/Source/ReeperCommon/ResourceUtil.cs @@ -0,0 +1,150 @@ +using System.IO; +using UnityEngine; +using ToolbarControl_NS; + +namespace ReeperCommon +{ + public static class ResourceUtil + { + public static bool SaveToDisk(this Texture2D texture, string pathInGameData) + { + System.Collections.Generic.List list = new System.Collections.Generic.List + { + TextureFormat.Alpha8, + TextureFormat.RGB24, + TextureFormat.RGBA32, + TextureFormat.ARGB32 + }; + if (!list.Contains(texture.format)) + { + return texture.CreateReadable().SaveToDisk(pathInGameData); + } + if (pathInGameData.StartsWith("/")) + { + pathInGameData = pathInGameData.Substring(1); + } + pathInGameData = "/GameData/" + pathInGameData; + if (!pathInGameData.EndsWith(".png")) + { + pathInGameData += ".png"; + } + bool result; + try + { + FileStream output = new FileStream(KSPUtil.ApplicationRootPath + pathInGameData, FileMode.OpenOrCreate, FileAccess.Write); + BinaryWriter binaryWriter = new BinaryWriter(output); + binaryWriter.Write(texture.EncodeToPNG()); + result = true; + } + catch (System.Exception) + { + result = false; + } + return result; + } + + public static Texture2D as2D(this Texture tex) + { + return tex as Texture2D; + } + + public static Texture2D LoadImage(string textureName, bool relativeToGameData = true) + { + if (relativeToGameData) + textureName = KSPUtil.ApplicationRootPath + "GameData/ScienceAlert/PluginData/Textures/" + textureName; + var texture2D = new Texture2D(2, 2); + if (!ToolbarControl.LoadImageFromFile(ref texture2D, textureName)) + Log.Debug("[ScienceAlert]:Failed to find texture '{0}'", textureName); + return texture2D; + } + + public static void FlipTexture(Texture2D tex, bool horizontal, bool vertical) + { + Color32[] pixels = tex.GetPixels32(); + Color32[] array = new Color32[pixels.Length]; + for (int i = 0; i < tex.height; i++) + { + for (int j = 0; j < tex.width; j++) + { + int num = (vertical ? tex.height - i - 1 : i) * tex.width + (horizontal ? tex.width - j - 1 : j); + array[i * tex.width + j] = pixels[num]; + } + } + tex.SetPixels32(array); + tex.Apply(); + } + + public static Texture2D CreateReadable(this Texture2D original) + { + if (original.width == 0 || original.height == 0) + { + throw new System.Exception("CreateReadable: Original has zero width or height or both"); + } + Texture2D texture2D = new Texture2D(original.width, original.height); + RenderTexture temporary = RenderTexture.GetTemporary(original.width, original.height, 0, RenderTextureFormat.ARGB32, RenderTextureReadWrite.sRGB, 1); + Graphics.Blit(original, temporary); + RenderTexture.active = temporary; + texture2D.ReadPixels(new Rect(0f, 0f, (float)texture2D.width, (float)texture2D.height), 0, 0); + RenderTexture.active = null; + RenderTexture.ReleaseTemporary(temporary); + return texture2D; + } + + public static Texture2D Cutout(this Texture2D source, Rect src, bool rectIsInUV = false) + { + Rect src2 = new Rect(src); + if (rectIsInUV) + { + src2.x *= (float)source.width; + src2.width *= (float)source.width; + src2.y *= (float)source.height; + src2.height *= (float)source.height; + } + return Cutout_Internal(source, src2); + } + + public static Texture2D Cutout(this Renderer renderer, Rect uv) + { + return ((Texture2D)renderer.sharedMaterial.mainTexture).Cutout(uv, true); + } + + private static Texture2D Cutout_Internal(Texture2D source, Rect src, bool secondAttempt = false) + { + Texture2D texture2D = new Texture2D(Mathf.FloorToInt(src.width), Mathf.FloorToInt(src.height), TextureFormat.ARGB32, false); + Texture2D result; + try + { + Color[] pixels = source.GetPixels(Mathf.FloorToInt(src.x), Mathf.FloorToInt(src.y), Mathf.FloorToInt(src.width), Mathf.FloorToInt(src.height)); + texture2D.SetPixels(pixels); + texture2D.Apply(); + result = texture2D; + } + catch (System.Exception) + { + result = secondAttempt ? null : Cutout_Internal(source.CreateReadable(), src, true); + } + return result; + } + + public static void GenerateRandom(this Texture2D tex) + { + Color32[] pixels = tex.GetPixels32(); + for (int i = 0; i < tex.height; i++) + { + for (int j = 0; j < tex.width; j++) + { + pixels[i * tex.width + j] = new Color(Random.Range(0f, 1f), Random.Range(0f, 1f), Random.Range(0f, 1f), Random.Range(0f, 1f)); + } + } + tex.SetPixels32(pixels); + tex.Apply(); + } + + public static Texture2D GenerateRandom(int w, int h) + { + Texture2D texture2D = new Texture2D(w, h, TextureFormat.ARGB32, false); + texture2D.GenerateRandom(); + return texture2D; + } + } +} diff --git a/Source/ScienceAlert.Experiments/BiomeFilter.cs b/Source/ScienceAlert.Experiments/BiomeFilter.cs new file mode 100644 index 0000000..35604fb --- /dev/null +++ b/Source/ScienceAlert.Experiments/BiomeFilter.cs @@ -0,0 +1,48 @@ +using ReeperCommon; +using UnityEngine; + +namespace ScienceAlert.Experiments +{ + public class BiomeFilter : MonoBehaviour + { + public bool GetCurrentBiome(out string biome) + { + biome = "N/A"; + + if (FlightGlobals.ActiveVessel == null) return false; + + string possibleBiome = string.Empty; + + if (GetBiome(FlightGlobals.ActiveVessel.latitude, FlightGlobals.ActiveVessel.longitude, out possibleBiome)) + { + biome = possibleBiome; + return true; + } + else + { + return false; + } + } + + public bool GetBiome(double lat, double lon, out string biome) + { + biome = string.Empty; + var vessel = FlightGlobals.ActiveVessel; + + if (vessel == null || vessel.mainBody.BiomeMap == null || vessel.mainBody.BiomeMap.MapName == null || vessel.mainBody.BiomeMap.Attributes.Length == 0) + { + return false; + } + + if (!string.IsNullOrEmpty(vessel.landedAt)) + { + biome = Vessel.GetLandedAtString(vessel.landedAt); + return true; + } + + biome = ScienceUtil.GetExperimentBiome(vessel.mainBody, lat, lon); + + return true; + } + } +} diff --git a/ScienceAlert.Experiments/EvaReportObserver.cs b/Source/ScienceAlert.Experiments/EvaReportObserver.cs similarity index 62% rename from ScienceAlert.Experiments/EvaReportObserver.cs rename to Source/ScienceAlert.Experiments/EvaReportObserver.cs index 86b036a..899eb24 100644 --- a/ScienceAlert.Experiments/EvaReportObserver.cs +++ b/Source/ScienceAlert.Experiments/EvaReportObserver.cs @@ -1,72 +1,105 @@ -using System.Collections.Generic; -using ReeperCommon; - -namespace ScienceAlert.Experiments -{ - internal class EvaReportObserver : RequiresCrew - { - public EvaReportObserver(StorageCache cache, ProfileData.ExperimentSettings settings, BiomeFilter filter, - ScanInterface scanInterface, string expid = "evaReport") - : base(cache, settings, filter, scanInterface, expid){} - - public override bool Deploy() - { - if (!Available || !IsReadyOnboard) return false; - if (FlightGlobals.ActiveVessel == null)return false; - - if (!FlightGlobals.ActiveVessel.isEVA) - { - if (FlightGlobals.getStaticPressure() > Settings.Instance.EvaAtmospherePressureWarnThreshold) - if (FlightGlobals.ActiveVessel.GetSrfVelocity().magnitude > Settings.Instance.EvaAtmosphereVelocityWarnThreshold) - { - DialogGUIBase[] options = new DialogGUIBase[2] - { - new DialogGUIButton("Science is worth a little risk", OnConfirmEva), - new DialogGUIButton("No, it would be a PR nightmare", null) - }; - - var multiOptionDialog = new MultiOptionDialog( - "It looks dangerous out there. Are you sure you want to send someone out? They might lose their grip!", - "It looks dangerous out there. Are you sure you want to send someone out? They might lose their grip!", - "Dangerous Condition Alert", - HighLogic.UISkin, options); - PopupDialog.SpawnPopupDialog(multiOptionDialog, false, HighLogic.UISkin); - return true; - } - return ExpelCrewman(); - } - - var evas = FlightGlobals.ActiveVessel.FindPartModulesImplementing(); - foreach (var exp in evas) - if (!exp.Deployed && exp.experimentID == experiment.id) - { - exp.DeployExperiment(); - break; - } - return true; - } - - protected void OnConfirmEva() - { - Log.Normal("EvaObserver: User confirmed eva despite conditions"); - Log.Normal("Expelling... {0}", ExpelCrewman() ? "success!" : "failed"); - } - - protected virtual bool ExpelCrewman() - { - List crewChoices = new List(); - - foreach (var crewable in crewableParts) - crewChoices.AddRange(crewable.protoModuleCrew); - - if (crewChoices.Count == 0) return false; - if (MapView.MapIsEnabled) MapView.ExitMapView(); - - if ((CameraManager.Instance.currentCameraMode & (CameraManager.CameraMode.Internal | CameraManager.CameraMode.IVA)) != 0) - CameraManager.Instance.SetCameraFlight(); - - var luckyKerbal = crewChoices[UnityEngine.Random.Range(0, crewChoices.Count - 1)]; - return FlightEVA.SpawnEVA(luckyKerbal.KerbalRef); - } - } -} \ No newline at end of file +using System.Collections.Generic; +using ReeperCommon; +using System.Linq; + +namespace ScienceAlert.Experiments +{ + internal class EvaReportObserver : RequiresCrew + { + public EvaReportObserver(StorageCache cache, ProfileData.ExperimentSettings settings, BiomeFilter filter, + ScanInterface scanInterface, string expid = "evaReport") + : base(cache, settings, filter, scanInterface, expid) { } + + public override bool Deploy() + { + if (!Available || !IsReadyOnboard) return false; + if (FlightGlobals.ActiveVessel == null) return false; + + if (!FlightGlobals.ActiveVessel.isEVA) + { + if (FlightGlobals.getStaticPressure() > Settings.Instance.EvaAtmospherePressureWarnThreshold) + if (FlightGlobals.ActiveVessel.GetSrfVelocity().magnitude > Settings.Instance.EvaAtmosphereVelocityWarnThreshold) + { + DialogGUIBase[] options = new DialogGUIBase[2] + { + new DialogGUIButton("Science is worth a little risk", OnConfirmEva), + new DialogGUIButton("No, it would be a PR nightmare", null) + }; + + var multiOptionDialog = new MultiOptionDialog( + "It looks dangerous out there. Are you sure you want to send someone out? They might lose their grip!", + "It looks dangerous out there. Are you sure you want to send someone out? They might lose their grip!", + "Dangerous Condition Alert", + HighLogic.UISkin, options); + PopupDialog.SpawnPopupDialog(multiOptionDialog, false, HighLogic.UISkin); + return true; + } + return ExpelCrewman(); + } + + var evas = FlightGlobals.ActiveVessel.FindPartModulesImplementing(); + for (int i = evas.Count - 1; i >= 0; i--) + { + ModuleScienceExperiment exp = evas[i]; + if (!exp.Deployed && exp.experimentID == experiment.id && !ExcludeFilters.IsExcluded(exp)) + { + //exp.DeployExperiment(); + if (!(DMagicFactory.DMagic_IsInstalled && DMagicFactory.RunExperiment(experiment.id, exp)) && + !(DMagicFactory.DMagicScienceAnimateGeneric_IsInstalled && DMagicFactory.RunSciAnimGenExperiment(experiment.id, exp))) + { + exp.DeployExperiment(); + } + break; + } + } + return true; + } + + + + protected void OnConfirmEva() + { + Log.Normal("EvaObserver: User confirmed eva despite conditions"); + Log.Normal("Expelling... {0}", ExpelCrewman() ? "success!" : "failed"); + } + + protected virtual bool ExpelCrewman() + { + List crewChoices = new List(); + + //crewChoices.AddRange(crewableParts[i].protoModuleCrew); + + for (int i = crewableParts.Count - 1; i >= 0; i--) + { + for (int i1 = crewableParts[i].protoModuleCrew.Count - 1; i1 >= 0; i1--) + { + if (crewableParts[i].protoModuleCrew[i1].type == ProtoCrewMember.KerbalType.Crew) + crewChoices.Add(crewableParts[i].protoModuleCrew[i1]); + } + } + if (crewChoices.Count == 0) return false; + if (MapView.MapIsEnabled) MapView.ExitMapView(); + + if ((CameraManager.Instance.currentCameraMode & (CameraManager.CameraMode.Internal | CameraManager.CameraMode.IVA)) != 0) + CameraManager.Instance.SetCameraFlight(); + + var luckyKerbal = crewChoices[UnityEngine.Random.Range(0, crewChoices.Count - 1)]; + return FlightEVA.SpawnEVA(luckyKerbal.KerbalRef); + } + + public override bool UpdateStatus(ExperimentSituations experimentSituation, out bool newReport) + { + newReport = false; + + // If the astronaut complex is level 0, EVA is only allowed when landed on the surface. + if (ScenarioUpgradeableFacilities.GetFacilityLevel(SpaceCenterFacility.AstronautComplex) == 0 && experimentSituation != ExperimentSituations.SrfLanded) + { + Available = false; + lastAvailableId = ""; + return false; + } + + return base.UpdateStatus(experimentSituation, out newReport); + } + } +} diff --git a/ScienceAlert.Experiments/ExperimentManager.cs b/Source/ScienceAlert.Experiments/ExperimentManager.cs similarity index 61% rename from ScienceAlert.Experiments/ExperimentManager.cs rename to Source/ScienceAlert.Experiments/ExperimentManager.cs index 480983c..7a68392 100644 --- a/ScienceAlert.Experiments/ExperimentManager.cs +++ b/Source/ScienceAlert.Experiments/ExperimentManager.cs @@ -1,237 +1,269 @@ -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Linq; -using UnityEngine; -using ReeperCommon; -using ScienceAlert.ProfileData; - -namespace ScienceAlert.Experiments -{ - using ProfileManager = ScienceAlertProfileManager; - using ExperimentObserverList = List; - - public class ExperimentManager : MonoBehaviour - { - // -------------------------------------------------------------------- - // Members of ExperimentManager - // -------------------------------------------------------------------- - private ScienceAlert scienceAlert; - private StorageCache vesselStorage; - private BiomeFilter biomeFilter; - - private System.Collections.IEnumerator watcher; - - ExperimentObserverList observers = new ExperimentObserverList(); - - string lastGoodBiome = string.Empty; // if BiomeFilter tells us the biome it got is probably not real, then we can use - - AudioPlayer audio; - - // -------------------------------------------------------------------- - // Events - // -------------------------------------------------------------------- - public delegate void ExperimentAvailableDelegate(ScienceExperiment experiment, float reportValue); // todo - public event ExperimentAvailableDelegate OnExperimentAvailable = delegate { }; // called whenever an experiment just became available in a new subject - - public event Callback OnObserversRebuilt = delegate { }; // called whenever observers are totally recreated from scratch, - public event Callback OnExperimentsScanned = delegate { }; // called whenever the observers rescan the ship, typically - - void Awake() - { - vesselStorage = gameObject.AddComponent(); - biomeFilter = gameObject.AddComponent(); - scienceAlert = gameObject.GetComponent(); - audio = GetComponent() ?? AudioPlayer.Audio; - - scienceAlert.OnScanInterfaceChanged += OnScanInterfaceChanged; - scienceAlert.OnToolbarButtonChanged += OnToolbarButtonChanged; - - GameEvents.onVesselWasModified.Add(OnVesselWasModified); - GameEvents.onVesselChange.Add(OnVesselChanged); - GameEvents.onVesselDestroy.Add(OnVesselDestroyed); - } - - void OnDestroy() - { - GameEvents.onVesselWasModified.Remove(OnVesselWasModified); - GameEvents.onVesselChange.Remove(OnVesselChanged); - GameEvents.onVesselDestroy.Remove(OnVesselDestroyed); - } - - public void Update() - { - if (FlightGlobals.ActiveVessel == null) return; - if (vesselStorage.IsBusy || watcher == null) return; - if (PauseMenu.isOpen) return; - if (watcher != null) watcher.MoveNext(); - } - - public void OnVesselWasModified(Vessel vessel) - { - if (vessel != FlightGlobals.ActiveVessel) return; - foreach (var obs in observers) - obs.Rescan(); - OnExperimentsScanned(); - } - - public void OnVesselChanged(Vessel newVessel) - { - RebuildObserverList(); - } - - public void OnVesselDestroyed(Vessel vessel) - { - try - { - if (FlightGlobals.fetch == null || FlightGlobals.ActiveVessel != vessel) return; - observers.Clear(); - watcher = null; - } - catch (Exception e) - { - Log.Error("Something has gone really wrong in ExperimentManager.OnVesselDestroyed: {0}", e); - observers.Clear(); - watcher = null; - } - } - - #region Experiment functions - - private System.Collections.IEnumerator UpdateObservers() - { - while (true) - { - if (!FlightGlobals.ready || FlightGlobals.ActiveVessel == null) - { - yield return 0; - continue; - } - var expSituation = ScienceUtil.GetExperimentSituation(FlightGlobals.ActiveVessel); - - foreach (var observer in observers) - { - try - { -#if PROFILE - float start = Time.realtimeSinceStartup; -#endif - bool newReport = false; - - // Is exciting new research available? - if (observer.UpdateStatus(expSituation, out newReport)) - { - // if we're timewarping, resume normal time if that setting was used - if (observer.StopWarpOnDiscovery || Settings.Instance.GlobalWarp == Settings.WarpSetting.GlobalOn) - if (Settings.Instance.GlobalWarp != Settings.WarpSetting.GlobalOff) - if (TimeWarp.CurrentRateIndex > 0) - { - OrbitSnapshot snap = new OrbitSnapshot(FlightGlobals.ActiveVessel.GetOrbitDriver().orbit); - TimeWarp.SetRate(0, true); - FlightGlobals.ActiveVessel.GetOrbitDriver().orbit = snap.Load(); - FlightGlobals.ActiveVessel.GetOrbitDriver().orbit.UpdateFromUT(Planetarium.GetUniversalTime()); - } - - scienceAlert.Button.Important = true; - - if (observer.settings.AnimationOnDiscovery) - scienceAlert.Button.PlayAnimation(); - else if (scienceAlert.Button.IsNormal) scienceAlert.Button.SetLit(); - - switch (Settings.Instance.SoundNotification) - { - case Settings.SoundNotifySetting.ByExperiment: - if (observer.settings.SoundOnDiscovery) - audio.PlayUI("bubbles", 2f); - break; - case Settings.SoundNotifySetting.Always: - audio.PlayUI("bubbles", 2f); - break; - } - OnExperimentAvailable(observer.Experiment, observer.NextReportValue); - } - else if (!observers.Any(ob => ob.Available)) - { - scienceAlert.Button.SetUnlit(); - scienceAlert.Button.Important = false; - } -#if PROFILE - Log.Warning("Tick time ({1}): {0} ms", (Time.realtimeSinceStartup - start) * 1000f, observer.ExperimentTitle); -#endif - } - catch (Exception e) - { - Log.Debug("ExperimentManager.UpdateObservers: exception {0}", e); - } - - if (TimeWarp.CurrentRate < Settings.Instance.TimeWarpCheckThreshold) - yield return 0; // pause until next frame - } // end observer loop - yield return 0; - } // end infinite while loop - } - - public int RebuildObserverList() - { - observers.Clear(); - ScanInterface scanInterface = GetComponent(); - - if (scanInterface == null) - Log.Error("ExperimentManager.RebuildObserverList: No ScanInterface component found"); // this is bad; things won't break if the scan interface - - // construct the experiment observer list ... - foreach (var expid in ResearchAndDevelopment.GetExperimentIDs()) - if (expid != "evaReport" && expid != "surfaceSample") // special cases - - if (FlightGlobals.ActiveVessel.FindPartModulesImplementing().Any(mse => mse.experimentID == expid)) - observers.Add(new ExperimentObserver(vesselStorage, ProfileManager.ActiveProfile[expid], biomeFilter, scanInterface, expid)); - - observers.Add(new SurfaceSampleObserver(vesselStorage, ProfileManager.ActiveProfile["surfaceSample"], biomeFilter, scanInterface)); - - try - { - if (ProfileManager.ActiveProfile["evaReport"].Enabled) - { - if (Settings.Instance.EvaReportOnTop) - { - observers = observers.OrderBy(obs => obs.ExperimentTitle).ToList(); - observers.Insert(0, new EvaReportObserver(vesselStorage, ProfileManager.ActiveProfile["evaReport"], biomeFilter, scanInterface)); - } - else - { - observers.Add(new EvaReportObserver(vesselStorage, ProfileManager.ActiveProfile["evaReport"], biomeFilter, scanInterface)); - observers = observers.OrderBy(obs => obs.ExperimentTitle).ToList(); - } - } - else observers = observers.OrderBy(obs => obs.ExperimentTitle).ToList(); - } - catch (NullReferenceException e) - { - Log.Error("ExperimentManager.RebuildObserverList: Active profile does not seem to have an \"evaReport\" entry; {0}", e); - } - - watcher = UpdateObservers(); // to prevent any problems by rebuilding in the middle of enumeration - OnObserversRebuilt(); - - return observers.Count; - } - - #endregion - - #region Message handling functions - - private void OnScanInterfaceChanged() - { - RebuildObserverList(); - } - - private void OnToolbarButtonChanged() - { - RebuildObserverList(); - } - - #endregion - - public ReadOnlyCollection Observers => new ReadOnlyCollection(observers); - } -} \ No newline at end of file +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using UnityEngine; +using ReeperCommon; +using ScienceAlert.ProfileData; + + +namespace ScienceAlert.Experiments +{ + using ProfileManager = ScienceAlertProfileManager; + using ExperimentObserverList = List; + + public class ExperimentManager : MonoBehaviour + { + // -------------------------------------------------------------------- + // Members of ExperimentManager + // -------------------------------------------------------------------- + private ScienceAlert scienceAlert; + private StorageCache vesselStorage; + private BiomeFilter biomeFilter; + + + private System.Collections.IEnumerator watcher; + + ExperimentObserverList observers = new ExperimentObserverList(); + + string lastGoodBiome = string.Empty; // if BiomeFilter tells us the biome it got is probably not real, then we can use + + AudioPlayer audio; + + // -------------------------------------------------------------------- + // Events + // -------------------------------------------------------------------- + public delegate void ExperimentAvailableDelegate(ScienceExperiment experiment, float reportValue); // todo + public event ExperimentAvailableDelegate OnExperimentAvailable = delegate { }; // called whenever an experiment just became available in a new subject + + public event Callback OnObserversRebuilt = delegate { }; // called whenever observers are totally recreated from scratch, + public event Callback OnExperimentsScanned = delegate { }; // called whenever the observers rescan the ship, typically + + void Awake() + { + Log.Write("ExperimentManager.Awake", Log.LEVEL.INFO); + + vesselStorage = gameObject.AddComponent(); + biomeFilter = GetComponent(); + scienceAlert = gameObject.GetComponent(); + audio = GetComponent() ?? AudioPlayer.Audio; + + scienceAlert.OnScanInterfaceChanged += OnScanInterfaceChanged; + scienceAlert.OnToolbarButtonChanged += OnToolbarButtonChanged; + + GameEvents.onVesselWasModified.Add(OnVesselWasModified); + GameEvents.onVesselChange.Add(OnVesselChanged); + GameEvents.onVesselDestroy.Add(OnVesselDestroyed); + } + + void OnDestroy() + { + GameEvents.onVesselWasModified.Remove(OnVesselWasModified); + GameEvents.onVesselChange.Remove(OnVesselChanged); + GameEvents.onVesselDestroy.Remove(OnVesselDestroyed); + } + + public void Update() + { + if (FlightGlobals.ActiveVessel == null) return; + if (vesselStorage.IsBusy || watcher == null) return; + if (PauseMenu.isOpen) return; + if (watcher != null) watcher.MoveNext(); + } + + public void OnVesselWasModified(Vessel vessel) + { + if (vessel != FlightGlobals.ActiveVessel) return; + for (int i = observers.Count - 1; i >= 0; i--) + observers[i].Rescan(); + OnExperimentsScanned(); + } + + public void OnVesselChanged(Vessel newVessel) + { + RebuildObserverList(); + } + + public void OnVesselDestroyed(Vessel vessel) + { + try + { + if (FlightGlobals.fetch == null || FlightGlobals.ActiveVessel != vessel) return; + observers.Clear(); + watcher = null; + } + catch (Exception e) + { + Log.Error("Something has gone really wrong in ExperimentManager.OnVesselDestroyed: {0}", e); + observers.Clear(); + watcher = null; + } + } + + #region Experiment functions + + private System.Collections.IEnumerator UpdateObservers() + { + Log.Write("ExperimentManager.UpdateObservers", Log.LEVEL.INFO); + + while (true) + { + while (!FlightGlobals.ready || FlightGlobals.ActiveVessel == null) + { + yield return 0; + //continue; + } + var expSituation = ScienceUtil.GetExperimentSituation(FlightGlobals.ActiveVessel); + if (observers.Count == 0) + { + ScienceAlert.Instance.SetUnlit(); + } + else + for (int i = observers.Count - 1; i >= 0; i--) + { + ExperimentObserver observer = observers[i]; + try + { +#if PROFILE + float start = Time.realtimeSinceStartup; +#endif + bool newReport = false; + + // Is exciting new research available? + if (observer.UpdateStatus(expSituation, out newReport)) + { + // if we're timewarping, resume normal time if that setting was used + if (observer.StopWarpOnDiscovery || Settings.Instance.GlobalWarp == Settings.WarpSetting.GlobalOn) + if (Settings.Instance.GlobalWarp != Settings.WarpSetting.GlobalOff) + if (TimeWarp.CurrentRateIndex > 0) + { + //OrbitSnapshot snap = new OrbitSnapshot(FlightGlobals.ActiveVessel.GetOrbitDriver().orbit); + TimeWarp.fetch.CancelAutoWarp(); + TimeWarp.SetRate(0, true); + //FlightGlobals.ActiveVessel.GetOrbitDriver().orbit = snap.Load(); + //FlightGlobals.ActiveVessel.GetOrbitDriver().orbit.UpdateFromUT(Planetarium.GetUniversalTime()); + } + +#if false + scienceAlert.Button.Important = true; +#endif + + if (observer.settings.AnimationOnDiscovery) + ScienceAlert.Instance.PlayAnimation(); + else ScienceAlert.Instance.SetLit(); // if (scienceAlert.Button.IsNormal) scienceAlert.Button.SetLit(); + + switch (Settings.Instance.SoundNotification) + { + case Settings.SoundNotifySetting.ByExperiment: + if (observer.settings.SoundOnDiscovery) + audio.PlayUI("bubbles", 2f); + break; + case Settings.SoundNotifySetting.Always: + audio.PlayUI("bubbles", 2f); + break; + } + OnExperimentAvailable(observer.Experiment, observer.NextReportValue); + } + else if (!observers.Any(ob => ob.Available)) + { + ScienceAlert.Instance.SetUnlit(); +#if false + scienceAlert.Button.Important = false; +#endif + + } +#if PROFILE + Log.Warning("Tick time ({1}): {0} ms", (Time.realtimeSinceStartup - start) * 1000f, observer.ExperimentTitle); +#endif + } + catch (Exception e) + { + Log.Debug("ExperimentManager.UpdateObservers: exception {0}", e); + } + + if (TimeWarp.CurrentRate < Settings.Instance.TimeWarpCheckThreshold) + yield return 0; // pause until next frame + } // end observer loop + yield return 0; + } // end infinite while loop + } + + public int RebuildObserverList() + { + Log.Write("ExperimentManager.RebuildObserverList", Log.LEVEL.INFO); + observers.Clear(); + if (!HighLogic.LoadedSceneIsFlight) + return 0; + ScanInterface scanInterface = GetComponent(); + + if (scanInterface == null) + Log.Error("ExperimentManager.RebuildObserverList: No ScanInterface component found"); // this is bad; things won't break if the scan interface + + // construct the experiment observer list ... + for (int eid = ResearchAndDevelopment.GetExperimentIDs().Count - 1; eid >= 0; eid--) + { + string expid = ResearchAndDevelopment.GetExperimentIDs()[eid]; + + if (expid != "evaReport" && expid != "surfaceSample") // special cases + { + var m = FlightGlobals.ActiveVessel.FindPartModulesImplementing().Where(mse => mse.experimentID == expid).ToList(); + if (m.Count > 0) + { + if (!ExcludeFilters.IsExcluded(m[0])) + { + observers.Add(new ExperimentObserver(vesselStorage, ProfileManager.ActiveProfile[expid], biomeFilter, scanInterface, expid, m[0])); + } + } + + + + } + } + observers.Add(new SurfaceSampleObserver(vesselStorage, ProfileManager.ActiveProfile["surfaceSample"], biomeFilter, scanInterface)); + try + { + if (ProfileManager.ActiveProfile["evaReport"].Enabled) + { + if (Settings.Instance.EvaReportOnTop) + { + observers = observers.OrderBy(obs => obs.ExperimentTitle).ToList(); + observers.Insert(0, new EvaReportObserver(vesselStorage, ProfileManager.ActiveProfile["evaReport"], biomeFilter, scanInterface)); + } + else + { + observers.Add(new EvaReportObserver(vesselStorage, ProfileManager.ActiveProfile["evaReport"], biomeFilter, scanInterface)); + observers = observers.OrderBy(obs => obs.ExperimentTitle).ToList(); + } + } + else observers = observers.OrderBy(obs => obs.ExperimentTitle).ToList(); + } + catch (NullReferenceException e) + { + Log.Error("ExperimentManager.RebuildObserverList: Active profile does not seem to have an \"evaReport\" entry; {0}", e); + } + + watcher = UpdateObservers(); // to prevent any problems by rebuilding in the middle of enumeration + OnObserversRebuilt(); + + return observers.Count; + } + + #endregion + + #region Message handling functions + + private void OnScanInterfaceChanged() + { + RebuildObserverList(); + } + + private void OnToolbarButtonChanged() + { + RebuildObserverList(); + } + + #endregion + + public ReadOnlyCollection Observers => new ReadOnlyCollection(observers); + } +} diff --git a/ScienceAlert.Experiments/ExperimentObserver.cs b/Source/ScienceAlert.Experiments/ExperimentObserver.cs similarity index 87% rename from ScienceAlert.Experiments/ExperimentObserver.cs rename to Source/ScienceAlert.Experiments/ExperimentObserver.cs index b4cb777..94ad101 100644 --- a/ScienceAlert.Experiments/ExperimentObserver.cs +++ b/Source/ScienceAlert.Experiments/ExperimentObserver.cs @@ -1,327 +1,332 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using UnityEngine; -using ReeperCommon; -using ScienceAlert.ProfileData; - -namespace ScienceAlert.Experiments -{ - using ScienceModuleList = List; - - public class ExperimentObserver - { - private ScienceModuleList modules; // all ModuleScienceExperiments onboard that represent our experiment - protected ScienceExperiment experiment; // The actual experiment that will be performed - protected StorageCache storage; // Represents possible storage locations on the vessel - public ExperimentSettings settings; // settings for this experiment - protected string lastAvailableId; // Id of the last time the experiment was available - protected string lastBiomeQuery; // the last good biome result we had - - protected BiomeFilter biomeFilter - ; // Provides a little more accuracy when it comes to determining current biome (the original biome map has some filtering done on it) - - protected ScanInterface scanInterface; // Determines whether we're allowed to know if an experiment is available - protected float nextReportValue; // take a guess - protected bool requireControllable; // Vessel needs to be controllable for the experiment to be available - - // events - public ExperimentManager.ExperimentAvailableDelegate OnAvailable = delegate { }; - - public ExperimentObserver(StorageCache cache, ExperimentSettings expSettings, BiomeFilter filter, - ScanInterface scanMapInterface, string expid) - { - settings = expSettings; - biomeFilter = filter; - requireControllable = true; - - if (scanMapInterface == null) - scanMapInterface = new DefaultScanInterface(); - - scanInterface = scanMapInterface; - - experiment = ResearchAndDevelopment.GetExperiment(expid); - - if (experiment == null) - Log.Error("Failed to get experiment '{0}'", expid); - - storage = cache; - Rescan(); - } - - ~ExperimentObserver() - { - - } - - public virtual void Rescan() - { - modules = new ScienceModuleList(); - if (FlightGlobals.ActiveVessel == null) return; - - ScienceModuleList potentials = FlightGlobals.ActiveVessel - .FindPartModulesImplementing(); - - foreach (var potential in potentials) - if (potential.experimentID == experiment.id) - modules.Add(potential); - } - - protected virtual float GetScienceTotal(ScienceSubject subject, out List data) - { - if (subject == null) - { - data = new List(); - return 0f; - } - - var found = storage.FindStoredData(subject.id); - data = found; - - if (found.Count == 0) - { - return subject.science; - } - float potentialScience = subject.science + - ResearchAndDevelopment.GetScienceValue(data.First().dataAmount, subject) * - HighLogic.CurrentGame.Parameters.Career.ScienceGainMultiplier; - - if (found.Count > 1) - { - float secondReport = - ResearchAndDevelopment.GetNextScienceValue(experiment.baseValue * experiment.dataScale, subject) * - HighLogic.CurrentGame.Parameters.Career.ScienceGainMultiplier; - potentialScience += secondReport; - if (found.Count > 2) - for (int i = 3; i < found.Count; ++i) - potentialScience += secondReport / Mathf.Pow(4f, i - 2); - } - return potentialScience; - } - - protected float GetBodyScienceValueMultipler(ExperimentSituations sit) - { - var b = FlightGlobals.currentMainBody; - switch (sit) - { - case ExperimentSituations.FlyingHigh: - return b.scienceValues.FlyingHighDataValue; - case ExperimentSituations.FlyingLow: - return b.scienceValues.FlyingLowDataValue; - case ExperimentSituations.InSpaceHigh: - return b.scienceValues.InSpaceHighDataValue; - case ExperimentSituations.InSpaceLow: - return b.scienceValues.InSpaceLowDataValue; - case ExperimentSituations.SrfLanded: - return b.scienceValues.LandedDataValue; - case ExperimentSituations.SrfSplashed: - return b.scienceValues.SplashedDataValue; - default: - return 0f; - } - } - - protected float CalculateNextReportValue(ScienceSubject subject, ExperimentSituations situation, - List stored) - { - if (stored.Count == 0) - return ResearchAndDevelopment.GetScienceValue(experiment.baseValue * experiment.dataScale, subject) * - HighLogic.CurrentGame.Parameters.Career.ScienceGainMultiplier; - - float experimentValue = - ResearchAndDevelopment.GetNextScienceValue(experiment.baseValue * experiment.dataScale, subject) * - HighLogic.CurrentGame.Parameters.Career.ScienceGainMultiplier; - - if (stored.Count == 1) return experimentValue; - return experimentValue / Mathf.Pow(4f, stored.Count - 1); - } - - public virtual bool UpdateStatus(ExperimentSituations experimentSituation, out bool newReport) - { - newReport = false; - - if (FlightGlobals.ActiveVessel == null) - { - Available = false; - lastAvailableId = ""; - return false; - } - - if (!settings.Enabled || (requireControllable && !FlightGlobals.ActiveVessel.IsControllable)) - { - Available = false; - lastAvailableId = ""; - return false; - } - - bool lastStatus = Available; - var vessel = FlightGlobals.ActiveVessel; - - if (!storage.IsBusy && IsReadyOnboard) - { - // does this experiment even apply in the current situation? - if (experiment.IsAvailableWhile(experimentSituation, vessel.mainBody)) - { - var biome = string.Empty; - if (experiment.BiomeIsRelevantWhile(experimentSituation)) - { - // biome matters; check to make sure we have biome data available - if (scanInterface.HaveScanData(vessel.latitude, vessel.longitude, vessel.mainBody)) - { - if (biomeFilter.GetBiome(vessel.latitude * Mathf.Deg2Rad, vessel.longitude * Mathf.Deg2Rad, - out biome)) - { - lastBiomeQuery = biome; - } - else - { - biome = lastBiomeQuery; // use last good known value - } - } - else - { - // no biome data available - Available = false; - lastAvailableId = ""; - return false; - } - } - - try - { - var subject = ResearchAndDevelopment.GetExperimentSubject(experiment, experimentSituation, - vessel.mainBody, biome, null); - List data = null; - float scienceTotal = GetScienceTotal(subject, out data); - - switch (settings.Filter) - { - case ExperimentSettings.FilterMethod.Unresearched: - // Fairly straightforward: total science + potential should be zero - Available = scienceTotal < 0.0005f; - break; - - case ExperimentSettings.FilterMethod.NotMaxed: - // <98% of science cap - Available = scienceTotal < subject.scienceCap * 0.98f * - HighLogic.CurrentGame.Parameters.Career.ScienceGainMultiplier; - break; - - case ExperimentSettings.FilterMethod.LessThanFiftyPercent: - Available = scienceTotal < subject.scienceCap * 0.5f * - HighLogic.CurrentGame.Parameters.Career.ScienceGainMultiplier; - break; - - case ExperimentSettings.FilterMethod.LessThanNinetyPercent: - Available = scienceTotal < subject.scienceCap * 0.9f * - HighLogic.CurrentGame.Parameters.Career.ScienceGainMultiplier; - break; - - default: // this should NEVER occur, but nice to have a safety measure - // in place if I add a filter option and forget to add its logic - Log.Error("Unrecognized experiment filter!"); - data = new List(); - break; - } - - nextReportValue = subject.CalculateNextReport(experiment, data); - Available = Available && nextReportValue > 0.01f; - Available = Available && nextReportValue > - ScienceAlertProfileManager.ActiveProfile.ScienceThreshold; - - if (Available) - { - if (lastAvailableId != subject.id) - { - lastStatus = - false; // force a refresh, in case we're going from available -> available in different subject id - newReport = true; // we've available on a brand new report - } - - lastAvailableId = subject.id; - } - } - catch (NullReferenceException e) - { - Log.Error( - "Failed to create {0} ScienceSubject. If you can manage to reproduce this error, let me know.", - experiment.id); - Log.Error("Exception was: {0}", e); - Available = lastStatus; - } - } - else - { - Available = false; - } - } - else Available = false; // no experiments ready - - return Available != lastStatus && Available; - } - - public virtual bool Deploy() - { - if (!Available) return false; - if (FlightGlobals.ActiveVessel == null) return false; - if (requireControllable && !FlightGlobals.ActiveVessel.IsControllable) return false; - - var deployable = GetNextOnboardExperimentModule(); - - if (!deployable) return false; - - try - { - deployable.GetType() - .InvokeMember("DeployExperiment", - System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.IgnoreReturn | - System.Reflection.BindingFlags.InvokeMethod, null, deployable, null); - } - catch (Exception e) - { - Log.Error( - "Failed to invoke \"DeployExperiment\" using GetType(), falling back to base type after encountering exception {0}", - e); - deployable.DeployExperiment(); - } - return true; - } - - - - #region Properties - - protected ModuleScienceExperiment GetNextOnboardExperimentModule() - { - foreach (var module in modules) - if (!module.Deployed && !module.Inoperable) - return module; - return null; - } - - public virtual bool IsReadyOnboard => GetNextOnboardExperimentModule() != null; - - - public virtual bool Available { get; protected set; } - - public string ExperimentTitle => experiment.experimentTitle; - - public virtual int OnboardExperimentCount => modules.Count; - - public bool SoundOnDiscovery => settings.SoundOnDiscovery; - - public bool AnimateOnDiscovery => settings.AnimationOnDiscovery; - - public bool StopWarpOnDiscovery => settings.StopWarpOnDiscovery; - - public float NextReportValue - { - get { return nextReportValue; } - private set { nextReportValue = value; } - } - - public ScienceExperiment Experiment => experiment; - - #endregion - } -} \ No newline at end of file +using System; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; +using ReeperCommon; +using ScienceAlert.ProfileData; + +namespace ScienceAlert.Experiments +{ + using ScienceModuleList = List; + + public class ExperimentObserver + { + private ScienceModuleList modules; // all ModuleScienceExperiments onboard that represent our experiment + protected ScienceExperiment experiment; // The actual experiment that will be performed + protected StorageCache storage; // Represents possible storage locations on the vessel + public ExperimentSettings settings; // settings for this experiment + protected string lastAvailableId; // Id of the last time the experiment was available + protected string lastBiomeQuery; // the last good biome result we had + + protected BiomeFilter biomeFilter; // Provides a little more accuracy when it comes to determining current biome (the original biome map has some filtering done on it) + + protected ScanInterface scanInterface; // Determines whether we're allowed to know if an experiment is available + protected float nextReportValue; // take a guess + protected bool requireControllable; // Vessel needs to be controllable for the experiment to be available + internal bool rerunnable = true; // Can the experiment be run again without having to reset it + internal bool resettable = true; // Whether an experiment can be reset - usually via the Part Action Window + // events + public ExperimentManager.ExperimentAvailableDelegate OnAvailable = delegate { }; + + public ExperimentObserver(StorageCache cache, ExperimentSettings expSettings, BiomeFilter filter, + ScanInterface scanMapInterface, string expid, ModuleScienceExperiment exp = null) + { + settings = expSettings; + biomeFilter = filter; + requireControllable = true; + if (exp != null) + { + rerunnable = exp.rerunnable; + resettable = exp.resettable; + } + + if (scanMapInterface == null) + scanMapInterface = new DefaultScanInterface(); + + scanInterface = scanMapInterface; + + experiment = ResearchAndDevelopment.GetExperiment(expid); + + if (experiment == null) + Log.Error("Failed to get experiment '{0}'", expid); + + storage = cache; + Rescan(); + } + + ~ExperimentObserver() + { + + } + + public virtual void Rescan() + { + modules = new ScienceModuleList(); + if (FlightGlobals.ActiveVessel == null) return; + + ScienceModuleList potentials = FlightGlobals.ActiveVessel + .FindPartModulesImplementing(); + + for (int i = potentials.Count - 1; i >= 0; i--) + { + ModuleScienceExperiment potential = potentials[i]; + if (potential.experimentID == experiment.id && !ExcludeFilters.IsExcluded(potential)) + { + modules.Add(potential); + } + } + } + + protected virtual float GetScienceTotal(ScienceSubject subject, out List data) + { + if (subject == null) + { + data = new List(); + return 0f; + } + + var found = storage.FindStoredData(subject.id); + data = found; + + if (found.Count == 0) + { + return subject.science; + } + float potentialScience = subject.science + + ResearchAndDevelopment.GetScienceValue(data.First().dataAmount, subject) * + HighLogic.CurrentGame.Parameters.Career.ScienceGainMultiplier; + + if (found.Count > 1) + { + float secondReport = + ResearchAndDevelopment.GetNextScienceValue(experiment.baseValue * experiment.dataScale, subject) * + HighLogic.CurrentGame.Parameters.Career.ScienceGainMultiplier; + potentialScience += secondReport; + if (found.Count > 2) + for (int i = 3; i < found.Count; ++i) + potentialScience += secondReport / Mathf.Pow(4f, i - 2); + } + return potentialScience; + } + + protected float GetBodyScienceValueMultipler(ExperimentSituations sit) + { + var b = FlightGlobals.currentMainBody; + switch (sit) + { + case ExperimentSituations.FlyingHigh: + return b.scienceValues.FlyingHighDataValue; + case ExperimentSituations.FlyingLow: + return b.scienceValues.FlyingLowDataValue; + case ExperimentSituations.InSpaceHigh: + return b.scienceValues.InSpaceHighDataValue; + case ExperimentSituations.InSpaceLow: + return b.scienceValues.InSpaceLowDataValue; + case ExperimentSituations.SrfLanded: + return b.scienceValues.LandedDataValue; + case ExperimentSituations.SrfSplashed: + return b.scienceValues.SplashedDataValue; + default: + return 0f; + } + } + + protected float CalculateNextReportValue(ScienceSubject subject, ExperimentSituations situation, + List stored) + { + if (stored.Count == 0) + return ResearchAndDevelopment.GetScienceValue(experiment.baseValue * experiment.dataScale, subject) * + HighLogic.CurrentGame.Parameters.Career.ScienceGainMultiplier; + + float experimentValue = + ResearchAndDevelopment.GetNextScienceValue(experiment.baseValue * experiment.dataScale, subject) * + HighLogic.CurrentGame.Parameters.Career.ScienceGainMultiplier; + + if (stored.Count == 1) return experimentValue; + return experimentValue / Mathf.Pow(4f, stored.Count - 1); + } + + public virtual bool UpdateStatus(ExperimentSituations experimentSituation, out bool newReport) + { + newReport = false; + + if (FlightGlobals.ActiveVessel == null) + { + Available = false; + lastAvailableId = ""; + return false; + } + + if (!settings.Enabled || (requireControllable && !FlightGlobals.ActiveVessel.IsControllable)) + { + Available = false; + lastAvailableId = ""; + return false; + } + + bool lastStatus = Available; + var vessel = FlightGlobals.ActiveVessel; + + if (!storage.IsBusy && IsReadyOnboard) + { + // does this experiment even apply in the current situation? + if (experiment.IsAvailableWhile(experimentSituation, vessel.mainBody)) + { + var biome = string.Empty; + if (experiment.BiomeIsRelevantWhile(experimentSituation)) + { + // biome matters; check to make sure we have biome data available + if (scanInterface.HaveScanData(vessel.latitude, vessel.longitude, vessel.mainBody)) + { + if (biomeFilter.GetBiome(vessel.latitude, vessel.longitude, + out biome)) + { + lastBiomeQuery = biome; + } + else + { + biome = lastBiomeQuery; // use last good known value + } + } + else + { + // no biome data available + Available = false; + lastAvailableId = ""; + return false; + } + } + + try + { + var subject = ResearchAndDevelopment.GetExperimentSubject(experiment, experimentSituation, + vessel.mainBody, biome, null); + List data = null; + float scienceTotal = GetScienceTotal(subject, out data); + + switch (settings.Filter) + { + case ExperimentSettings.FilterMethod.Unresearched: + // Fairly straightforward: total science + potential should be zero + Available = scienceTotal < 0.0005f; + break; + + case ExperimentSettings.FilterMethod.NotMaxed: + // <98% of science cap + Available = scienceTotal < subject.scienceCap * 0.98f * + HighLogic.CurrentGame.Parameters.Career.ScienceGainMultiplier; + break; + + case ExperimentSettings.FilterMethod.LessThanFiftyPercent: + Available = scienceTotal < subject.scienceCap * 0.5f * + HighLogic.CurrentGame.Parameters.Career.ScienceGainMultiplier; + break; + + case ExperimentSettings.FilterMethod.LessThanNinetyPercent: + Available = scienceTotal < subject.scienceCap * 0.9f * + HighLogic.CurrentGame.Parameters.Career.ScienceGainMultiplier; + break; + + default: // this should NEVER occur, but nice to have a safety measure + // in place if I add a filter option and forget to add its logic + Log.Error("Unrecognized experiment filter!"); + data = new List(); + break; + } + + nextReportValue = subject.CalculateNextReport(experiment, data); + Available = Available && nextReportValue > 0.01f; + if (ScienceAlertProfileManager.ActiveProfile != null) + Available = Available && nextReportValue > + ScienceAlertProfileManager.ActiveProfile.ScienceThreshold; + + if (Available) + { + if (lastAvailableId != subject.id) + { + lastStatus = + false; // force a refresh, in case we're going from available -> available in different subject id + newReport = true; // we've available on a brand new report + } + + lastAvailableId = subject.id; + } + } + catch (NullReferenceException e) + { + Log.Error( + "Failed to create {0} ScienceSubject. If you can manage to reproduce this error, let me know.", + experiment.id); + Log.Error("Exception was: {0}", e); + Available = lastStatus; + } + } + else + { + Available = false; + } + } + else Available = false; // no experiments ready + + return Available != lastStatus && Available; + } + + public virtual bool Deploy() + { + if (!Available) return false; + if (FlightGlobals.ActiveVessel == null) return false; + if (requireControllable && !FlightGlobals.ActiveVessel.IsControllable) return false; + + var exp = GetNextOnboardExperimentModule(); + + if (!exp) return false; + + + + if (!(DMagicFactory.DMagic_IsInstalled && DMagicFactory.RunExperiment(experiment.id, exp)) && + !(DMagicFactory.DMagicScienceAnimateGeneric_IsInstalled && DMagicFactory.RunSciAnimGenExperiment(experiment.id, exp))) + { + exp.DeployExperiment(); + return true; + } + return false; + } + #region Properties + + protected ModuleScienceExperiment GetNextOnboardExperimentModule() + { + for (int i = modules.Count - 1; i >= 0; i--) + { + ModuleScienceExperiment module = modules[i]; + if (!module.Deployed && !module.Inoperable) + return module; + } + return null; + } + + public virtual bool IsReadyOnboard => GetNextOnboardExperimentModule() != null; + + + public virtual bool Available { get; protected set; } + + public string ExperimentTitle => experiment.experimentTitle; + + public virtual int OnboardExperimentCount => modules.Count; + + public bool SoundOnDiscovery => settings.SoundOnDiscovery; + + public bool AnimateOnDiscovery => settings.AnimationOnDiscovery; + + public bool StopWarpOnDiscovery => settings.StopWarpOnDiscovery; + + public float NextReportValue + { + get { return nextReportValue; } + private set { nextReportValue = value; } + } + + public ScienceExperiment Experiment => experiment; + + #endregion + } +} diff --git a/ScienceAlert.Experiments/RequiresCrew.cs b/Source/ScienceAlert.Experiments/RequiresCrew.cs similarity index 59% rename from ScienceAlert.Experiments/RequiresCrew.cs rename to Source/ScienceAlert.Experiments/RequiresCrew.cs index a5be00f..efd36e5 100644 --- a/ScienceAlert.Experiments/RequiresCrew.cs +++ b/Source/ScienceAlert.Experiments/RequiresCrew.cs @@ -1,42 +1,52 @@ -using System.Collections.Generic; -using ReeperCommon; - -namespace ScienceAlert.Experiments -{ - class RequiresCrew : ExperimentObserver - { - protected List crewableParts = new List(); - - public RequiresCrew(StorageCache cache, ProfileData.ExperimentSettings settings, BiomeFilter filter, - ScanInterface scanInterface, string expid) - : base(cache, settings, filter, scanInterface, expid) - { - requireControllable = false; - } - - public override void Rescan() - { - base.Rescan(); - crewableParts.Clear(); - if (FlightGlobals.ActiveVessel == null) return; - - FlightGlobals.ActiveVessel.parts.ForEach(p => - { - if (p.CrewCapacity > 0) crewableParts.Add(p); - }); - - } - - - public override bool IsReadyOnboard - { - get - { - foreach (var crewable in crewableParts) - if (crewable.protoModuleCrew.Count > 0) - return true; - return false; - } - } - } -} \ No newline at end of file +using System.Collections.Generic; +using ReeperCommon; + +namespace ScienceAlert.Experiments +{ + class RequiresCrew : ExperimentObserver + { + protected List crewableParts = new List(); + + public RequiresCrew(StorageCache cache, ProfileData.ExperimentSettings settings, BiomeFilter filter, + ScanInterface scanInterface, string expid) + : base(cache, settings, filter, scanInterface, expid) + { + requireControllable = false; + } + + public override void Rescan() + { + base.Rescan(); + crewableParts.Clear(); + if (FlightGlobals.ActiveVessel == null) return; + + FlightGlobals.ActiveVessel.parts.ForEach(p => + { + if (p.CrewCapacity > 0) crewableParts.Add(p); + }); + + } + + + public override bool IsReadyOnboard + { + get + { + for (int i = crewableParts.Count - 1; i >= 0; i--) + { + //if (crewableParts[i].protoModuleCrew.Count > 0) + // return true; + for (int i1 = crewableParts[i].protoModuleCrew.Count - 1; i1 >= 0; i1--) + { + if (crewableParts[i].protoModuleCrew[i1].type == ProtoCrewMember.KerbalType.Crew) + { + return true; + } + } + + } + return false; + } + } + } +} diff --git a/ScienceAlert.Experiments/SurfaceSampleObserver.cs b/Source/ScienceAlert.Experiments/SurfaceSampleObserver.cs similarity index 52% rename from ScienceAlert.Experiments/SurfaceSampleObserver.cs rename to Source/ScienceAlert.Experiments/SurfaceSampleObserver.cs index 038c875..6a8a5c0 100644 --- a/ScienceAlert.Experiments/SurfaceSampleObserver.cs +++ b/Source/ScienceAlert.Experiments/SurfaceSampleObserver.cs @@ -1,23 +1,39 @@ - -namespace ScienceAlert.Experiments -{ - internal class SurfaceSampleObserver : EvaReportObserver - { - public SurfaceSampleObserver(StorageCache cache, ProfileData.ExperimentSettings settings, BiomeFilter filter, - ScanInterface scanInterface) - : base(cache, settings, filter, scanInterface, "surfaceSample") - { - } - - public override bool IsReadyOnboard - { - get - { - if (FlightGlobals.ActiveVessel == null) return false; - if (FlightGlobals.ActiveVessel.isEVA) - return GetNextOnboardExperimentModule() != null; - return Settings.Instance.CheckSurfaceSampleNotEva && base.IsReadyOnboard; - } - } - } -} \ No newline at end of file + +namespace ScienceAlert.Experiments +{ + internal class SurfaceSampleObserver : EvaReportObserver + { + public SurfaceSampleObserver(StorageCache cache, ProfileData.ExperimentSettings settings, BiomeFilter filter, + ScanInterface scanInterface) + : base(cache, settings, filter, scanInterface, "surfaceSample") + { + } + + public override bool IsReadyOnboard + { + get + { + if (FlightGlobals.ActiveVessel == null) return false; + if (FlightGlobals.ActiveVessel.isEVA) + return GetNextOnboardExperimentModule() != null; + return Settings.Instance.CheckSurfaceSampleNotEva && base.IsReadyOnboard; + } + } + + public override bool UpdateStatus(ExperimentSituations experimentSituation, out bool newReport) + { + newReport = false; + + // Surface samples are not allowed until both astronaut complex and r&d facility reach level 1. + if (ScenarioUpgradeableFacilities.GetFacilityLevel(SpaceCenterFacility.AstronautComplex) == 0 || + ScenarioUpgradeableFacilities.GetFacilityLevel(SpaceCenterFacility.ResearchAndDevelopment) == 0) + { + Available = false; + lastAvailableId = ""; + return false; + } + + return base.UpdateStatus(experimentSituation, out newReport); + } + } +} diff --git a/Source/ScienceAlert.ProfileData/ExperimentSettings.cs b/Source/ScienceAlert.ProfileData/ExperimentSettings.cs new file mode 100644 index 0000000..8c4c76d --- /dev/null +++ b/Source/ScienceAlert.ProfileData/ExperimentSettings.cs @@ -0,0 +1,163 @@ +using ReeperCommon; + +namespace ScienceAlert.ProfileData +{ + public class ExperimentSettings + { + public enum FilterMethod + { + Unresearched, + NotMaxed, + LessThanFiftyPercent, + LessThanNinetyPercent + } + + private bool _enabled = true; + private bool _soundOnDiscovery = true; + private bool _animationOnDiscovery = true; + private bool _stopWarpOnDiscovery; + private FilterMethod _filter; + public bool IsDefault; + public event Callback OnChanged = delegate{}; + + public bool Enabled + { + get + { + return _enabled; + } + set + { + if (value == _enabled) return; + _enabled = value; + OnChanged(); + } + } + + public bool SoundOnDiscovery + { + get + { + return _soundOnDiscovery; + } + set + { + if (_soundOnDiscovery != value) + { + _soundOnDiscovery = value; + OnChanged(); + } + } + } + + public bool AnimationOnDiscovery + { + get + { + return _animationOnDiscovery; + } + set + { + if (value != _animationOnDiscovery) + { + _animationOnDiscovery = value; + OnChanged(); + } + } + } + + public bool StopWarpOnDiscovery + { + get + { + return _stopWarpOnDiscovery; + } + set + { + if (value != _stopWarpOnDiscovery) + { + _stopWarpOnDiscovery = value; + OnChanged(); + } + } + } + + public FilterMethod Filter + { + get + { + return _filter; + } + set + { + if (value != _filter) + { + _filter = value; + OnChanged(); + } + } + } + + public ExperimentSettings() + { + } + + public ExperimentSettings(ExperimentSettings other) + { + Enabled = other.Enabled; + SoundOnDiscovery = other.SoundOnDiscovery; + AnimationOnDiscovery = other.AnimationOnDiscovery; + StopWarpOnDiscovery = other.StopWarpOnDiscovery; + Filter = other.Filter; + IsDefault = other.IsDefault; + } + + public void OnLoad(ConfigNode node) + { + Enabled = node.Parse("Enabled", true); + SoundOnDiscovery = node.Parse("SoundOnDiscovery", true); + AnimationOnDiscovery = node.Parse("AnimationOnDiscovery", true); + StopWarpOnDiscovery = node.Parse("StopWarpOnDiscovery", false); + string value = node.GetValue("Filter"); + if (string.IsNullOrEmpty(value)) + { + Log.Debug("[ScienceAlert]:Settings: invalid experiment filter"); + value = System.Enum.GetValues(typeof(FilterMethod)).GetValue(0).ToString(); + } + Filter = (FilterMethod)System.Enum.Parse(typeof(FilterMethod), value); + IsDefault = node.Parse("IsDefault", false); + } + + public void OnSave(ConfigNode node) + { + node.AddValue("Enabled", Enabled); + node.AddValue("SoundOnDiscovery", SoundOnDiscovery); + node.AddValue("AnimationOnDiscovery", AnimationOnDiscovery); + node.AddValue("StopWarpOnDiscovery", StopWarpOnDiscovery); + node.AddValue("Filter", Filter); + node.AddValue("IsDefault", IsDefault); + } + + public override bool Equals(object obj) + { + return obj is ExperimentSettings es && Enabled == es.Enabled && SoundOnDiscovery == + es.SoundOnDiscovery && AnimationOnDiscovery == es.AnimationOnDiscovery && + StopWarpOnDiscovery == es.StopWarpOnDiscovery && Filter == es.Filter && + IsDefault == es.IsDefault; + } + + public override int GetHashCode() + { + return (Enabled ? 0x1 : 0x0) | (SoundOnDiscovery ? 0x2 : 0x0) | + (AnimationOnDiscovery ? 0x4 : 0x0) | (StopWarpOnDiscovery ? 0x8 : 0x0) | + (IsDefault ? 0x10 : 0x0) | ((int)Filter << 8); + } + + public override string ToString() + { + ConfigNode configNode = new ConfigNode(); + OnSave(configNode); + return configNode.ToString(); + } + } +} diff --git a/Source/ScienceAlert.ProfileData/Profile.cs b/Source/ScienceAlert.ProfileData/Profile.cs new file mode 100644 index 0000000..7beb439 --- /dev/null +++ b/Source/ScienceAlert.ProfileData/Profile.cs @@ -0,0 +1,208 @@ +using System.Collections.Generic; +using ReeperCommon; +using UnityEngine; + +namespace ScienceAlert.ProfileData +{ + internal class Profile + { + [Persistent(isPersistant = true)] + public string name = string.Empty; + + [Persistent] + public bool modified; + + [Persistent] + public float scienceThreshold; + + [System.NonSerialized] + public Dictionary settings; + + public ExperimentSettings this[string expid] + { + get + { + if (settings.ContainsKey(expid)) + { + return settings[expid]; + } + settings[expid] = new ExperimentSettings(); + return settings[expid]; + } + private set + { + settings.Add(expid.ToLower(), value); + } + } + + public string DisplayName + { + get + { + if (modified) + { + return "*" + name + "*"; + } + return name; + } + } + + public float ScienceThreshold + { + get + { + return scienceThreshold; + } + set + { + if (value != scienceThreshold) + { + modified = true; + } + scienceThreshold = value; + } + } + + public Profile(ConfigNode node) + { + Setup(); + OnLoad(node); + RegisterEvents(); + } + + public Profile(string name) + { + Log.Debug("VERB ALERT:Creating profile '{0}' with default values", name); + this.name = name; + Setup(); + RegisterEvents(); + } + + public Profile(Profile other) + { + Dictionary.KeyCollection keys = other.settings.Keys; + settings = new Dictionary(); + + foreach (string current in keys) + { + settings.Add(current, new ExperimentSettings(other.settings[current])); + } + name = string.Copy(other.name); + modified = other.modified; + scienceThreshold = other.scienceThreshold; + RegisterEvents(); + } + + private void Setup() + { + settings = new Dictionary(); + try + { + List experimentIDs = ResearchAndDevelopment.GetExperimentIDs(); + for (int i = experimentIDs.Count - 1; i >= 0; i--) + { + string current = experimentIDs[i]; + + settings.Add(current, new ExperimentSettings()); + } + } + catch (System.Exception ex) + { + Log.Debug("[ScienceAlert]:Profile '{1}' constructor exception: {0}", ex, string.IsNullOrEmpty(name) ? "(unnamed)" : name); + } + } + + public void OnSave(ConfigNode node, bool writeContents) + { + ConfigNode.CreateConfigFromObject(this, 0, node); + if (writeContents) + { + foreach (KeyValuePair current in settings) + { + ConfigNode newNode = new ConfigNode(current.Key); + node.AddNode(newNode); + current.Value.OnSave(newNode); + } + } + //Log.Debug("ALERT:Profile: OnSave config: {0}", node.ToString()); + } + + public void OnLoad(ConfigNode node) + { + Log.Debug("ALERT:Loading profile..."); + ConfigNode.LoadObjectFromConfig(this, node); + if (string.IsNullOrEmpty(name)) + { + name = "nameless." + System.Guid.NewGuid(); + } + else + { + Log.Debug("ALERT:Profile name is '{0}'", name); + } + string[] array = node.nodes.DistinctNames(); + for (int i = 0; i < array.Length; i++) + { + string text = array[i]; + ConfigNode node2 = node.GetNode(text); + if (!settings.ContainsKey(text)) + { + settings.Add(text, new ExperimentSettings()); + } + settings[text].OnLoad(node2); + } + } + + public override bool Equals(object obj) + { + bool eql = false; + IDictionary otherSettings; + if (obj is Profile other && name == other.name && Mathf.Approximately( + scienceThreshold, other.scienceThreshold) && (otherSettings = other.settings). + Count == settings.Count) + { + eql = true; + foreach (var pair in settings) + { + if (!otherSettings.TryGetValue(pair.Key, out ExperimentSettings os) || + !pair.Value.Equals(os)) + { + eql = false; + break; + } + // If all of ours exist in theirs, and same size, then they can have no + // imposters + } + } + return eql; + } + + public override int GetHashCode() + { + return name.GetHashCode(); + } + + public Profile Clone() + { + return new Profile(this); + } + + public static Profile MakeDefault() + { + return new Profile("default"); + } + + private void SettingChanged() + { + Log.Debug("ALERT:Profile '{0}' was modified!", name); + modified = true; + } + + private void RegisterEvents() + { + foreach (KeyValuePair current in settings) + { + current.Value.OnChanged += SettingChanged; + } + } + } +} diff --git a/ScienceAlert.ProfileData/ScienceAlertProfileManager.cs b/Source/ScienceAlert.ProfileData/ScienceAlertProfileManager.cs similarity index 85% rename from ScienceAlert.ProfileData/ScienceAlertProfileManager.cs rename to Source/ScienceAlert.ProfileData/ScienceAlertProfileManager.cs index 0d1069e..da48c00 100644 --- a/ScienceAlert.ProfileData/ScienceAlertProfileManager.cs +++ b/Source/ScienceAlert.ProfileData/ScienceAlertProfileManager.cs @@ -1,426 +1,473 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using ReeperCommon; - -namespace ScienceAlert.ProfileData -{ - using ProfileTable = Dictionary; - using VesselTable = Dictionary; - - [KSPScenario(ScenarioCreationOptions.AddToExistingCareerGames | - ScenarioCreationOptions.AddToExistingScienceSandboxGames | - ScenarioCreationOptions.AddToNewCareerGames | - ScenarioCreationOptions.AddToNewScienceSandboxGames, - GameScenes.FLIGHT)] - class ScienceAlertProfileManager : ScenarioModule - { - private readonly string ProfileStoragePath = ConfigUtil.GetDllDirectoryPath() + "/profiles.cfg"; - ProfileTable storedProfiles; - VesselTable vesselProfiles; - - private const string PERSISTENT_NODE_NAME = "ScienceAlert_Profiles"; - private const string STORED_NODE_NAME = "Stored_Profiles"; - - #region intialization/deinitialization - - public override void OnAwake() - { - base.OnAwake(); - if (HighLogic.CurrentGame.config == null) - HighLogic.CurrentGame.config = new ConfigNode(); - - Settings.Instance.OnSave += OnSettingsSave; // this triggers saving of stored profiles - - GameEvents.onVesselChange.Add(OnVesselChange); - GameEvents.onVesselDestroy.Add(OnVesselDestroy); - GameEvents.onVesselCreate.Add(OnVesselCreate); - GameEvents.onVesselWasModified.Add(OnVesselModified); - GameEvents.onFlightReady.Add(OnFlightReady); - GameEvents.onVesselWillDestroy.Add(OnVesselWillDestroy); - GameEvents.onSameVesselUndock.Add(OnSameVesselUndock); - GameEvents.onUndock.Add(OnUndock); - - Ready = false; // won't be ready until OnLoad - Instance = this; - LoadStoredProfiles(); - } - - private void OnDestroy() - { - Instance = null; - GameEvents.onVesselChange.Remove(OnVesselChange); - GameEvents.onVesselDestroy.Remove(OnVesselDestroy); - GameEvents.onVesselCreate.Remove(OnVesselCreate); - GameEvents.onVesselWasModified.Remove(OnVesselModified); - GameEvents.onFlightReady.Remove(OnFlightReady); - GameEvents.onVesselWillDestroy.Remove(OnVesselWillDestroy); - GameEvents.onSameVesselUndock.Remove(OnSameVesselUndock); - GameEvents.onUndock.Remove(OnUndock); - SaveStoredProfiles(); - } - - private void LoadStoredProfiles() - { - try - { - storedProfiles = new ProfileTable(); - if (System.IO.File.Exists(ProfileStoragePath)) - { - ConfigNode stored = ConfigNode.Load(ProfileStoragePath); - if (stored != null && stored.HasNode(STORED_NODE_NAME)) - { - stored = stored.GetNode(STORED_NODE_NAME); // to avoid having an empty cfg, which will cause KSP to hang at load - var profiles = stored.GetNodes("PROFILE"); - - foreach (var profileNode in profiles) - { - try - { - Profile p = new Profile(profileNode); - p.modified = false; // by definition, stored profiles haven't been modified - storedProfiles.Add(p.name, p); - Log.Normal("[ScienceAlert]Loaded profile '{0}' successfully!", p.name); - } - catch (Exception e) - { - Log.Error("ProfileManager: profile '{0}' failed to parse; {1}", name, e); - } - } - } - } - if (DefaultProfile == null) - storedProfiles.Add("default", Profile.MakeDefault()); - - } - catch (Exception e) - { - Log.Error("ProfileManager: Exception loading stored profiles: {0}", e); - storedProfiles = new ProfileTable(); - } - } - - private void SaveStoredProfiles() - { - ConfigNode profiles = new ConfigNode(STORED_NODE_NAME); // note: gave it a name because an empty ConfigNode will cause KSP to choke on load - foreach (var kvp in storedProfiles) - { - try - { - kvp.Value.OnSave(profiles.AddNode(new ConfigNode("PROFILE"))); - } - catch (Exception e) - { - Log.Error("ProfileManager: Exception while saving '{0}': {1}", kvp.Key, e); - } - } - System.IO.File.WriteAllText(ProfileStoragePath, profiles.ToString()); - } - - #endregion - - #region GameEvents - - private void OnVesselChange(Vessel vessel) - { - if (vessel == null) return; - if (!vesselProfiles.ContainsKey(vessel.id)) return; - if (vesselProfiles[vessel.id].modified) return; - - var stored = FindStoredProfile(vesselProfiles[vessel.id].name); - if (stored == null) - { - vesselProfiles[vessel.id].modified = true; - } - else - { - Log.Normal("ProfileManager.OnVesselChange: Bringing vessel {0} up to date on stored profile {1}", vessel.id, stored.name); - vesselProfiles[vessel.id] = stored.Clone(); - } - } - - private void OnVesselDestroy(Vessel vessel) - { - if (!vesselProfiles.ContainsKey(vessel.id)) return; - vesselProfiles.Remove(vessel.id); - } - - private void OnVesselCreate(Vessel newVessel) - { - if (newVessel == null) return; - - try - { - if (vesselProfiles == null) return; // we haven't even init yet - if (!newVessel.loaded) return; - if (newVessel.protoVessel == null) return; - if (newVessel.protoVessel.protoPartSnapshots.Count == 0) return; - if (FlightGlobals.ActiveVessel == newVessel || newVessel.vesselType == VesselType.Debris) return; - - Profile parentProfile = null; - uint mid = newVessel.packed ? newVessel.protoVessel.protoPartSnapshots[newVessel.protoVessel.rootIndex].missionID : newVessel.rootPart.missionID; - - if (mid == FlightGlobals.ActiveVessel.rootPart.missionID) - if (vesselProfiles.ContainsKey(FlightGlobals.ActiveVessel.id)) - if (vesselProfiles[FlightGlobals.ActiveVessel.id] == ActiveProfile) - parentProfile = ActiveProfile; - - if (parentProfile == null) - { - var parentVessel = FlightGlobals.Vessels.SingleOrDefault(v => - { - if (v.rootPart == null) return false; - if (mid != v.rootPart.missionID) return false; - return vesselProfiles.ContainsKey(v.id); - }); - - if (parentVessel != null) parentProfile = vesselProfiles[parentVessel.id]; - } - - if (parentProfile == null) return; - if (vesselProfiles.ContainsKey(newVessel.id)) return; - vesselProfiles.Add(newVessel.id, parentProfile.Clone()); - } - catch (Exception e) - { - Log.Error("ProfileManager.OnVesselCreate: Something went wrong while handling this event; {0}", e); - } - } - - private void OnVesselModified(Vessel vessel) - { - Log.Debug("ProfileManager.OnVesselModified: {0}", vessel.vesselName); - } - - private void OnFlightReady() - { - Log.Debug("ProfileManager.OnFlightReady"); - } - - private void OnVesselWillDestroy(Vessel vessel) - { - Log.Debug("ProfileManager.OnVesselWillDestroy: {0}", vessel.vesselName); - } - - private void OnSameVesselUndock(GameEvents.FromToAction nodes) - { - Log.Debug("ProfileManager.OnSameVesselUndock: from {0}, to {1}", nodes.from.vessel.vesselName, nodes.to.vessel.vesselName); - } - - private void OnUndock(EventReport report) - { - Log.Debug("ProfileManager.OnUndock: origin {0}, sender {1}", report.origin.name, report.sender); - } - - public override void OnLoad(ConfigNode node) - { - base.OnLoad(node); - - if (!node.HasNode(PERSISTENT_NODE_NAME)) - { - Log.Warning("Persistent save has no saved profiles"); - vesselProfiles = new VesselTable(); - Ready = true; - return; - } - node = node.GetNode(PERSISTENT_NODE_NAME); - vesselProfiles = new VesselTable(); - var guidStrings = node.nodes.DistinctNames(); - - foreach (var strGuid in guidStrings) - { - try - { - Guid guid = new Guid(strGuid); // could throw an exception if string is malformed - if (!FlightGlobals.Vessels.Any(v => v.id == guid)) continue; - if (vesselProfiles.ContainsKey(guid)) continue; - - ConfigNode profileNode = node.GetNode(strGuid); - Profile p = new Profile(profileNode); - - if (p.modified) - vesselProfiles.Add(guid, p); - else - { - if (HaveStoredProfile(p.name)) - { - vesselProfiles.Add(guid, FindStoredProfile(p.name).Clone()); - } - else - { - p.modified = true; - vesselProfiles.Add(guid, p); - } - } - } - catch (Exception e) - { - Log.Error("ProfileManager: Exception while loading '{0}': {1}", strGuid, e); - } - } - Ready = true; - } - - public override void OnSave(ConfigNode node) - { - base.OnSave(node); - if (!node.HasNode(PERSISTENT_NODE_NAME)) node.AddNode(PERSISTENT_NODE_NAME); - node = node.GetNode(PERSISTENT_NODE_NAME); - - foreach (var kvp in vesselProfiles) - { - try - { - if (FlightGlobals.Vessels.Any(v => v.id == kvp.Key)) - kvp.Value.OnSave(node.AddNode(new ConfigNode(kvp.Key.ToString()))); - } - catch (Exception e) - { - Log.Error("ProfileManager.OnSave: Exception while saving profile '{0}': {1}", - $"{kvp.Key}:{kvp.Value.name}", e); - } - } - } - - #endregion - - #region other events - - public void OnSettingsSave() - { - SaveStoredProfiles(); - } - - #endregion - - #region Interaction methods - public static ScienceAlertProfileManager Instance { private set; get; } - public bool Ready { private set; get; } - - public static Profile DefaultProfile - { - get - { - var key = Instance.storedProfiles.Keys.SingleOrDefault(k => k.ToLower().Equals("default")); - if (string.IsNullOrEmpty(key)) - Instance.storedProfiles.Add(key, Profile.MakeDefault()); - return Instance.storedProfiles[key]; - } - } - - public static Profile ActiveProfile - { - get - { - var vessel = FlightGlobals.ActiveVessel; - - if (vessel == null) - { - return null; - } - if (!Instance.vesselProfiles.ContainsKey(vessel.id)) - { - Instance.vesselProfiles.Add(vessel.id, DefaultProfile.Clone()); - } - return Instance.vesselProfiles[vessel.id]; - } - } - - public static bool HasActiveProfile => FlightGlobals.ActiveVessel != null; - - public static int Count => Instance.storedProfiles != null ? Instance.storedProfiles.Count : 0; - - public static ProfileTable.KeyCollection Names => Instance.storedProfiles.Keys; - - public static Profile GetProfileByName(string name) - { - return FindStoredProfile(name); - } - - public static ProfileTable Profiles => Instance.storedProfiles; - - public static void StoreActiveProfile(string name) - { - Profile p = ActiveProfile; - p.name = name; - p.modified = false; - Profile newProfile = p.Clone(); - Instance.storedProfiles.Add(name, newProfile); - } - - public static void DeleteProfile(string name) - { - var p = FindStoredProfile(name); - if (p == null) return; - Instance.storedProfiles.Remove(name); - } - - public static void RenameProfile(string oldName, string newName) - { - var p = FindStoredProfile(oldName); - - if (p == null) return; - if (DefaultProfile.Equals(p)) - { - var cloned = p.Clone(); - cloned.name = newName; - AssignAsActiveProfile(cloned); - cloned.modified = p.modified; - if (!cloned.modified) - StoreActiveProfile(newName); - } - else - { - p.name = newName; - } - } - - public static bool LoadStoredAsActiveProfile(string name) - { - var p = FindStoredProfile(name); - if (p == null) return false; - if (FlightGlobals.ActiveVessel == null) return false; - Profile newProfile = p.Clone(); - newProfile.modified = false; // should already be false, just making sure - Instance.vesselProfiles[FlightGlobals.ActiveVessel.id] = newProfile; - return true; - } - - public static bool AssignAsActiveProfile(Profile p) - { - var vessel = FlightGlobals.ActiveVessel; - if (vessel == null) return false; - if (p == null) return false; - Instance.vesselProfiles[vessel.id] = p; - return true; - } - - #endregion - - #region internal methods - - private static Profile FindStoredProfile(string name) - { - var key = Instance.storedProfiles.Keys.SingleOrDefault(k => k.ToLower().Equals(name.ToLower())); - return string.IsNullOrEmpty(key) ? null : Instance.storedProfiles[key]; - } - - public static bool HaveStoredProfile(string name) - { - return FindStoredProfile(name) != null; - } - - private string FindVesselName(Guid guid) - { - Vessel vessel = FlightGlobals.Vessels.SingleOrDefault(v => v.id == guid); - if (vessel == null) return $""; - return vessel.vesselName; - } - - private string VesselIdentifier(Guid guid) - { - return $"{guid}:{FindVesselName(guid)}"; - } - #endregion - } -} \ No newline at end of file +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using ReeperCommon; +using UnityEngine; + +namespace ScienceAlert.ProfileData +{ + using ProfileTable = Dictionary; + using VesselTable = Dictionary; + + [KSPScenario(ScenarioCreationOptions.AddToExistingCareerGames | + ScenarioCreationOptions.AddToExistingScienceSandboxGames | + ScenarioCreationOptions.AddToNewCareerGames | + ScenarioCreationOptions.AddToNewScienceSandboxGames, + GameScenes.FLIGHT)] + class ScienceAlertProfileManager : ScenarioModule + { + private readonly string ProfileStoragePath = ConfigUtil.GetDllDirectoryPath() + "/../PluginData/profiles.cfg"; + ProfileTable storedProfiles; + VesselTable vesselProfiles; + + private const string PERSISTENT_NODE_NAME = "ScienceAlert_Profiles"; + private const string STORED_NODE_NAME = "Stored_Profiles"; + + #region intialization/deinitialization + + public override void OnAwake() + { + base.OnAwake(); + if (HighLogic.CurrentGame.config == null) + HighLogic.CurrentGame.config = new ConfigNode(); + + Settings.Instance.OnSave += OnSettingsSave; // this triggers saving of stored profiles + + GameEvents.onVesselChange.Add(OnVesselChange); + GameEvents.onVesselDestroy.Add(OnVesselDestroy); + GameEvents.onVesselCreate.Add(OnVesselCreate); +#if false + GameEvents.onVesselWasModified.Add(OnVesselModified); + GameEvents.onFlightReady.Add(OnFlightReady); + GameEvents.onVesselWillDestroy.Add(OnVesselWillDestroy); + GameEvents.onSameVesselUndock.Add(OnSameVesselUndock); + GameEvents.onUndock.Add(OnUndock); +#endif + + Ready = false; // won't be ready until OnLoad + Instance = this; + LoadStoredProfiles(); + } + + private void OnDestroy() + { + Instance = null; + GameEvents.onVesselChange.Remove(OnVesselChange); + GameEvents.onVesselDestroy.Remove(OnVesselDestroy); + GameEvents.onVesselCreate.Remove(OnVesselCreate); +#if false + GameEvents.onVesselWasModified.Remove(OnVesselModified); + GameEvents.onFlightReady.Remove(OnFlightReady); + GameEvents.onVesselWillDestroy.Remove(OnVesselWillDestroy); + GameEvents.onSameVesselUndock.Remove(OnSameVesselUndock); + GameEvents.onUndock.Remove(OnUndock); +#endif + SaveStoredProfiles(); + } + + private void LoadStoredProfiles() + { + try + { + storedProfiles = new ProfileTable(); + if (System.IO.File.Exists(ProfileStoragePath)) + { + ConfigNode stored = ConfigNode.Load(ProfileStoragePath); + if (stored != null && stored.HasNode(STORED_NODE_NAME)) + { + stored = stored.GetNode(STORED_NODE_NAME); // to avoid having an empty cfg, which will cause KSP to hang at load + var profiles = stored.GetNodes("PROFILE"); + + foreach (var profileNode in profiles) + { + try + { + Profile p = new Profile(profileNode); + p.modified = false; // by definition, stored profiles haven't been modified + storedProfiles.Add(p.name, p); + Log.Normal("[ScienceAlert]Loaded profile '{0}' successfully!", p.name); + } + catch (Exception e) + { + Log.Error("ProfileManager: profile '{0}' failed to parse; {1}", name, e); + } + } + } + } + if (DefaultProfile == null) + storedProfiles.Add("default", Profile.MakeDefault()); + + } + catch (Exception e) + { + Log.Error("ProfileManager: Exception loading stored profiles: {0}", e); + storedProfiles = new ProfileTable(); + } + } + + private void SaveStoredProfiles() + { + ConfigNode profiles = new ConfigNode(STORED_NODE_NAME); // note: gave it a name because an empty ConfigNode will cause KSP to choke on load + foreach (var kvp in storedProfiles) + { + try + { + kvp.Value.OnSave(profiles.AddNode(new ConfigNode("PROFILE")), true); + } + catch (Exception e) + { + Log.Error("ProfileManager: Exception while saving '{0}': {1}", kvp.Key, e); + } + } + System.IO.File.WriteAllText(ProfileStoragePath, profiles.ToString()); + } + +#endregion + +#region GameEvents + + private void OnVesselChange(Vessel vessel) + { + if (vessel == null) return; + StartCoroutine(WaitForLoad(vessel)); + } + IEnumerator WaitForLoad(Vessel vessel) + { + while (!Ready) + yield return new WaitForSeconds(0.1f); + + if (!vesselProfiles.ContainsKey(vessel.id)) yield break; + if (vesselProfiles[vessel.id].modified) yield break; + + var stored = FindStoredProfile(vesselProfiles[vessel.id].name); + if (stored == null) + { + vesselProfiles[vessel.id].modified = true; + } + else + { + Log.Normal("ProfileManager.OnVesselChange: Bringing vessel {0} up to date on stored profile {1}", vessel.id, stored.name); + vesselProfiles[vessel.id] = stored.Clone(); + } + } + + private void OnVesselDestroy(Vessel vessel) + { + if (!vesselProfiles.ContainsKey(vessel.id)) return; + vesselProfiles.Remove(vessel.id); + } + + private void OnVesselCreate(Vessel newVessel) + { + if (newVessel == null) return; + + try + { + if (vesselProfiles == null) return; // we haven't even init yet + if (!newVessel.loaded) return; + if (newVessel.protoVessel == null) return; + if (newVessel.protoVessel.protoPartSnapshots.Count == 0) return; + if (FlightGlobals.ActiveVessel == newVessel || newVessel.vesselType == VesselType.Debris) return; + + Profile parentProfile = null; + uint mid = newVessel.packed ? newVessel.protoVessel.protoPartSnapshots[newVessel.protoVessel.rootIndex].missionID : newVessel.rootPart.missionID; + + if (mid == FlightGlobals.ActiveVessel.rootPart.missionID) + if (vesselProfiles.ContainsKey(FlightGlobals.ActiveVessel.id)) + if (vesselProfiles[FlightGlobals.ActiveVessel.id] == ActiveProfile) + parentProfile = ActiveProfile; + + if (parentProfile == null) + { + var parentVessel = FlightGlobals.Vessels.SingleOrDefault(v => + { + if (v.rootPart == null) return false; + if (mid != v.rootPart.missionID) return false; + return vesselProfiles.ContainsKey(v.id); + }); + + if (parentVessel != null) parentProfile = vesselProfiles[parentVessel.id]; + } + + if (parentProfile == null) return; + if (vesselProfiles.ContainsKey(newVessel.id)) return; + vesselProfiles.Add(newVessel.id, parentProfile.Clone()); + } + catch (Exception e) + { + Log.Error("ProfileManager.OnVesselCreate: Something went wrong while handling this event; {0}", e); + } + } + +#if false + private void OnVesselModified(Vessel vessel) + { + Log.Debug("ProfileManager.OnVesselModified: {0}", vessel.vesselName); + } + + private void OnFlightReady() + { + Log.Debug("ProfileManager.OnFlightReady"); + } + + private void OnVesselWillDestroy(Vessel vessel) + { + Log.Debug("ProfileManager.OnVesselWillDestroy: {0}", vessel.vesselName); + } + + private void OnSameVesselUndock(GameEvents.FromToAction nodes) + { + Log.Debug("ProfileManager.OnSameVesselUndock: from {0}, to {1}", nodes.from.vessel.vesselName, nodes.to.vessel.vesselName); + } + + private void OnUndock(EventReport report) + { + Log.Debug("ProfileManager.OnUndock: origin {0}, sender {1}", report.origin.name, report.sender); + } +#endif + + public override void OnLoad(ConfigNode node) + { + base.OnLoad(node); + + if (!node.HasNode(PERSISTENT_NODE_NAME)) + { + Log.Warning("Persistent save has no saved profiles"); + vesselProfiles = new VesselTable(); + Ready = true; + return; + } + savedNode = node.CreateCopy(); + StartCoroutine(WaitForFlightGlobals()); + } + ConfigNode savedNode; + + IEnumerator WaitForFlightGlobals() + { + while (!FlightGlobals.ready || (FlightGlobals.ActiveVessel == null)) + yield return new WaitForSeconds(0.1f); + ConfigNode node = savedNode; + + node = node.GetNode(PERSISTENT_NODE_NAME); + vesselProfiles = new VesselTable(); + var guidStrings = node.nodes.DistinctNames(); + + foreach (var strGuid in guidStrings) + { + try + { + Guid guid = new Guid(strGuid); // could throw an exception if string is malformed + + + if (!FlightGlobals.fetch.vessels.Any(v => v.id == guid)) continue; + + if (vesselProfiles.ContainsKey(guid)) continue; + + ConfigNode profileNode = node.GetNode(strGuid); + Profile p = new Profile(profileNode); + + if (p.modified) + vesselProfiles.Add(guid, p); + else + { + var storedProfile = FindStoredProfile(p.name); + if (storedProfile != null) + { + vesselProfiles.Add(guid, storedProfile.Clone()); + } + else + { + p.modified = true; + vesselProfiles.Add(guid, p); + } + } + } + catch (Exception e) + { + Log.Error("ProfileManager: Exception while loading '{0}': {1}", strGuid, e); + } + } + Ready = true; + } + + public override void OnSave(ConfigNode node) + { + if (!HighLogic.LoadedSceneIsGame || HighLogic.CurrentGame == null) + return; + base.OnSave(node); + var seen = new HashSet(Instance.storedProfiles.Count * 2); + if (!node.HasNode(PERSISTENT_NODE_NAME)) node.AddNode(PERSISTENT_NODE_NAME); + node = node.GetNode(PERSISTENT_NODE_NAME); + + foreach (var kvp in vesselProfiles) + { + Guid uid = kvp.Key; + var profile = kvp.Value; + try + { + if (FlightGlobals.Vessels.Any(v => v.id == uid)) + { + var newNode = new ConfigNode(uid.ToString()); + node.AddNode(newNode); + profile.OnSave(newNode, profile.modified || !seen.Contains(profile. + name)); + seen.Add(profile.name); + } + } + catch (Exception e) + { + Log.Error("ProfileManager.OnSave: Exception while saving profile '{0}': {1}", + $"{uid}:{profile.name}", e); + } + } + seen.Clear(); + } + +#endregion + +#region other events + + public void OnSettingsSave() + { + SaveStoredProfiles(); + } + +#endregion + +#region Interaction methods + public static ScienceAlertProfileManager Instance { private set; get; } + public bool Ready { private set; get; } + + public static Profile DefaultProfile + { + get + { + var key = Instance.storedProfiles.Keys.SingleOrDefault(k => k.ToLower().Equals("default")); + if (string.IsNullOrEmpty(key)) + { + key = "default"; + Instance.storedProfiles.Add(key, Profile.MakeDefault()); + } + return Instance.storedProfiles[key]; + } + } + + public static Profile ActiveProfile + { + get + { + if (HighLogic.LoadedScene == GameScenes.SPACECENTER || FlightGlobals.ActiveVessel == null || Instance.vesselProfiles ==null) + return null; + var vessel = FlightGlobals.ActiveVessel; + + if (!Instance.vesselProfiles.ContainsKey(vessel.id)) + { + Instance.vesselProfiles.Add(vessel.id, DefaultProfile.Clone()); + } + return Instance.vesselProfiles[vessel.id]; + } + } + + public static bool HasActiveProfile => FlightGlobals.ActiveVessel != null; + + public static int Count => Instance.storedProfiles != null ? Instance.storedProfiles.Count : 0; + + public static ProfileTable.KeyCollection Names => Instance.storedProfiles.Keys; + + public static Profile GetProfileByName(string name) + { + return FindStoredProfile(name); + } + + public static ProfileTable Profiles => Instance.storedProfiles; + + public static void StoreActiveProfile(string name) + { + Profile p = ActiveProfile; + p.name = name; + p.modified = false; + Profile newProfile = p.Clone(); + + // If a profile already exists with this name (e.g. if saving the active profile and the name is unchanged) then remove it first. + DeleteProfile(name); + + Instance.storedProfiles.Add(name, newProfile); + } + + public static void DeleteProfile(string name) + { + var p = FindStoredProfile(name); + if (p == null) return; + Instance.storedProfiles.Remove(name); + } + + public static void RenameProfile(string oldName, string newName) + { + var p = FindStoredProfile(oldName); + + if (p == null) return; + if (DefaultProfile.Equals(p)) + { + var cloned = p.Clone(); + cloned.name = newName; + AssignAsActiveProfile(cloned); + cloned.modified = p.modified; + if (!cloned.modified) + StoreActiveProfile(newName); + } + else + { + p.name = newName; + } + } + + public static bool LoadStoredAsActiveProfile(string name) + { + var p = FindStoredProfile(name); + if (p == null) return false; + if (FlightGlobals.ActiveVessel == null) return false; + Profile newProfile = p.Clone(); + newProfile.modified = false; // should already be false, just making sure + Instance.vesselProfiles[FlightGlobals.ActiveVessel.id] = newProfile; + return true; + } + + public static bool AssignAsActiveProfile(Profile p) + { + var vessel = FlightGlobals.ActiveVessel; + if (vessel == null) return false; + if (p == null) return false; + Instance.vesselProfiles[vessel.id] = p; + return true; + } + +#endregion + +#region internal methods + + private static Profile FindStoredProfile(string name) + { + var key = Instance.storedProfiles.Keys.SingleOrDefault(k => k.ToLower().Equals(name.ToLower())); + return string.IsNullOrEmpty(key) ? null : Instance.storedProfiles[key]; + } + + public static bool HaveStoredProfile(string name) + { + return FindStoredProfile(name) != null; + } + + private string FindVesselName(Guid guid) + { + Vessel vessel = FlightGlobals.Vessels.SingleOrDefault(v => v.id == guid); + if (vessel == null) return $""; + return vessel.vesselName; + } + + private string VesselIdentifier(Guid guid) + { + return $"{guid}:{FindVesselName(guid)}"; + } +#endregion + } +} diff --git a/Source/ScienceAlert.Windows/DraggableDebugWindow.cs b/Source/ScienceAlert.Windows/DraggableDebugWindow.cs new file mode 100644 index 0000000..f4a7800 --- /dev/null +++ b/Source/ScienceAlert.Windows/DraggableDebugWindow.cs @@ -0,0 +1,38 @@ +#if DEBUGWINDOW +using ReeperCommon; +using UnityEngine; + +namespace ScienceAlert.Windows +{ + internal class DraggableDebugWindow : DraggableWindow + { + protected override Rect Setup() + { + Title = "Debug"; + Skin = Settings.Skin; + Settings.Instance.OnSave += new Settings.Callback(AboutToSave); + LoadFrom(Settings.Instance.additional.GetNode("DebugWindow") ?? new ConfigNode()); + Log.Debug("ALERT:DraggableDebugWindow.Setup"); + return new Rect(windowRect.x, windowRect.y, 256f, 128f); + } + + private void AboutToSave() + { + Log.Debug("ALERT:DraggableDebugWindow.AboutToSave"); + SaveInto(Settings.Instance.additional.GetNode("DebugWindow") ?? Settings.Instance.additional.AddNode("DebugWindow")); + } + + protected override void DrawUI() + { + GUILayout.BeginVertical(GUILayout.ExpandHeight(true), GUILayout.MinHeight(128f)); + GUILayout.Label("Biome: to be implemented", GUILayout.MinWidth(256f)); + GUILayout.EndVertical(); + } + + protected override void OnCloseClick() + { + Visible = false; + } + } +} +#endif diff --git a/Source/ScienceAlert.Windows/DraggableExperimentList.cs b/Source/ScienceAlert.Windows/DraggableExperimentList.cs new file mode 100644 index 0000000..79f8d02 --- /dev/null +++ b/Source/ScienceAlert.Windows/DraggableExperimentList.cs @@ -0,0 +1,241 @@ +using System.Collections.Generic; +using System.Collections; +using System.Linq; +using ReeperCommon; +using ScienceAlert.Experiments; +using UnityEngine; +using KSP.Localization; + +namespace ScienceAlert.Windows +{ + class DraggableExperimentList : DraggableWindow + { + internal static DraggableExperimentList Instance; + private const string WindowTitle = "Available Experiments"; + + public ExperimentManager manager; + public BiomeFilter biomeFilter; + public ScanInterface scanInterface; + + private bool adjustedSkin; + + protected override Rect Setup() + { + Instance = this; + Title = Localizer.Format("#ScienceAlert_title");//"Available Experiments" + ShrinkHeightToFit = true; + Skin = Instantiate(Settings.Skin); // we'll be altering it a little bit to make sure the buttons are the right size + + //Skin = Object.Instantiate(HighLogic.Skin); + + Settings.Instance.OnSave += AboutToSave; + LoadFrom(Settings.Instance.additional.GetNode("ExperimentWindow") ?? new ConfigNode()); + return new Rect(windowRect.x, windowRect.y, 256f, 128f); + } + + private void AboutToSave() + { + SaveInto(Settings.Instance.additional.GetNode("ExperimentWindow") ?? Settings.Instance.additional.AddNode("ExperimentWindow")); + } + + private void LateUpdate() + { + if (FlightGlobals.ActiveVessel != null) + if (Settings.Instance.DisplayCurrentBiome) + { + // if SCANsat is enabled, don't show biome names for unscanned areas + if (Settings.Instance.ScanInterfaceType == Settings.ScanInterface.ScanSat && scanInterface != null) + { + if (!scanInterface.HaveScanData(FlightGlobals.ActiveVessel.latitude, FlightGlobals.ActiveVessel.longitude, FlightGlobals.ActiveVessel.mainBody)) + { + Title = Localizer.Format("#ScienceAlert_HaveScanData");//"Data not found" + return; + } + } + Title = GetBiomeString(); + return; + } + Title = WindowTitle; // default experiment window title + } + + private string GetBiomeString() + { + string biome = Title; + if (biomeFilter.GetCurrentBiome(out biome)) + { + return biome; + } + return WindowTitle; + } + + protected new void OnGUI() + { + if (!adjustedSkin) + { + Skin.window.stretchHeight = true; + List experimentTitles = new List(); + + ResearchAndDevelopment.GetExperimentIDs().ForEach(id => experimentTitles.Add(ResearchAndDevelopment.GetExperiment(id).experimentTitle)); + Skin.button.fixedWidth = Mathf.Max(64f, experimentTitles.Max(title => + { + float minWidth = 0f; + float maxWidth = 0f; + Skin.button.CalcMinMaxWidth(new GUIContent(title + " (123.4)"), out minWidth, out maxWidth); + return maxWidth; + })); + + adjustedSkin = true; + } + base.OnGUI(); + } + + internal List msc; + + internal void CheckForCollection() + { + msc = new List(); + + if (FlightGlobals.ActiveVessel != null) + { + //var parts = FlightGlobals.ActiveVessel.Parts.FindAll(p => p.Modules.Contains("ModuleScienceContainer")); + //Part part = parts[0]; + var parts = FlightGlobals.ActiveVessel.Parts.FindAll(p => p.Modules.Contains("ModuleScienceContainer")); + for (int i = parts.Count - 1; i >= 0; i--) + { + Part part = parts[i]; + + var m = part.Modules["ModuleScienceContainer"] as ModuleScienceContainer; + if (m != null && m.Events["CollectAllEvent"].guiActive) + { + msc.Add(m); + } + } + } + } + + + + public int AnyAvailableScienceContainers() + { + int dataCnt = 0; + if (FlightGlobals.ActiveVessel != null && FlightGlobals.ActiveVessel.Parts.Count > 1) //a single Kerbal doesn't have a "collect all" action. + { + Vessel activeVessel = FlightGlobals.ActiveVessel; + + var parts = FlightGlobals.ActiveVessel.Parts.FindAll(p => p.Modules.Contains("ModuleScienceContainer")); + + // make sure there is capacity available + for (int i = parts.Count - 1; i >= 0; i--) + { + var m = parts[i].Modules["ModuleScienceContainer"] as ModuleScienceContainer; + if (m.capacity == 0 || m.GetStoredDataCount() < m.capacity) + return 1; + } + return 0; + } + return dataCnt; + } + + bool doAll = false; + bool noEva = false; + protected override void DrawUI() + { + var expSituation = ScienceUtil.GetExperimentSituation(FlightGlobals.ActiveVessel); + + GUILayout.BeginVertical(); + { + var observers = manager.Observers; + + if (observers.All(eo => !eo.Available)) + { + GUILayout.Label(Localizer.Format("#ScienceAlert_Available"));//"(no experiments available)" + } + else + { + doAll = false; + if (GUILayout.Button(Localizer.Format("#ScienceAlert_DeployAll"), Settings.Skin.button))//"Deploy All" + { + doAll = true; + noEva = false; + } + + if (ScenarioUpgradeableFacilities.GetFacilityLevel(SpaceCenterFacility.AstronautComplex) > 0 || expSituation == ExperimentSituations.SrfLanded) + { + if (GUILayout.Button(Localizer.Format("#ScienceAlert_DeployexEVA"), Settings.Skin.button, GUILayout.Height(35)))//"Deploy All except EVA" + { + doAll = true; + noEva = true; + } + } + } + if (AnyAvailableScienceContainers() > 0) + { + if (msc != null && msc.Count > 0) + { + + if (GUILayout.Button(Localizer.Format("#ScienceAlert_CollectAll"), Settings.Skin.button))//"Collect All" + { + foreach (var m in msc) + { + m.CollectAllEvent(); + } + } + } + else + { + GUI.enabled = false; + GUILayout.Button(Localizer.Format("#ScienceAlert_Nocontainers"));//"Collect All (no science containers available)" + GUI.enabled = true; + } + } + + if (ScenarioUpgradeableFacilities.GetFacilityLevel(SpaceCenterFacility.AstronautComplex) == 0 && (expSituation != ExperimentSituations.SrfLanded || + !FlightGlobals.ActiveVessel.mainBody.isHomeWorld)) + noEva = true; + //----------------------------------------------------- + // Experiment list + //----------------------------------------------------- + + for (int i = observers.Count() - 1; i >= 0; i--) + { + if (observers[i].Available) + { + var content = new GUIContent(observers[i].ExperimentTitle); + color = ""; + if (!observers[i].rerunnable) color = lblYellowColor; + if (!observers[i].resettable) color = lblRedColor; + if (Settings.Instance.ShowReportValue) content.text += $" ({observers[i].NextReportValue:0.#})"; + if (color != "") + content.text = Colorized(color, content.text); + + if (noEva && observers[i].Experiment.id == "evaReport") + continue; + if (!doAll && !GUILayout.Button(content, Settings.Skin.button, GUILayout.ExpandHeight(false))) + continue; + + Log.Debug("Deploying {0}", observers[i].ExperimentTitle); + AudioPlayer.Audio.PlayUI("click2"); + observers[i].Deploy(); + } + } + } + + GUILayout.EndVertical(); + } + //string lblGreenColor = "00ff00"; + //string lblDrkGreenColor = "ff9d00"; + //string lblBlueColor = "3DB1FF"; + string lblYellowColor = "FFD966"; + string lblRedColor = "f90000"; + + string color = ""; + string Colorized(string color, string txt) + { + return string.Format("{1}", color, txt); + } + protected override void OnCloseClick() + { + Visible = false; + } + } +} diff --git a/Source/ScienceAlert.Windows/DraggableOptionsWindow.cs b/Source/ScienceAlert.Windows/DraggableOptionsWindow.cs new file mode 100644 index 0000000..2f91cf7 --- /dev/null +++ b/Source/ScienceAlert.Windows/DraggableOptionsWindow.cs @@ -0,0 +1,624 @@ +using System.Collections.Generic; +using System.Linq; +using ReeperCommon; +using ScienceAlert.Experiments; +using ScienceAlert.ProfileData; +//ing ScienceAlert.Toolbar; +using UnityEngine; +using KSP.Localization; + +namespace ScienceAlert.Windows +{ + public class DraggableOptionsWindow : DraggableWindow + { + internal enum OpenPane + { + None, + AdditionalOptions, + LoadProfiles + } + + private Vector2 scrollPos; // = default(Vector2); + private Vector2 additionalScrollPos; // = default(Vector2); + private Vector2 profileScrollPos; // = Vector2.zero; + private readonly Dictionary experimentIds = new Dictionary(); + private readonly List filterList = new List(); + private string thresholdValue = "0"; + private OpenPane submenu; + public ScienceAlert scienceAlert; + public ExperimentManager manager; + private Texture2D collapseButton; // = new Texture2D(24, 24); + private Texture2D expandButton; // = new Texture2D(24, 24); + private Texture2D openButton; // = new Texture2D(24, 24); + private Texture2D saveButton; // = new Texture2D(24, 24); + private Texture2D deleteButton; // = new Texture2D(24, 24); + private Texture2D renameButton; // = new Texture2D(24, 24); + private Texture2D blackPixel; // = new Texture2D(1, 1); + private GUISkin whiteLabel; + private System.Globalization.NumberFormatInfo formatter; + private GUIStyle miniLabelLeft; + private GUIStyle miniLabelRight; + private GUIStyle miniLabelCenter; + private AudioPlayer audio; + internal string editText = string.Empty; + internal string lockName = string.Empty; + internal Profile editProfile; + internal PopupDialog popup; + internal string badChars = "()[]?'\":#$%^&*~;\n\t\r!@,.{}/<>"; + + protected override Rect Setup() + { + scrollPos = default(Vector2); + additionalScrollPos = default(Vector2); + profileScrollPos = Vector2.zero; + + collapseButton = new Texture2D(24, 24); + expandButton = new Texture2D(24, 24); + openButton = new Texture2D(24, 24); + saveButton = new Texture2D(24, 24); + deleteButton = new Texture2D(24, 24); + renameButton = new Texture2D(24, 24); + blackPixel = new Texture2D(1, 1); + + formatter = (System.Globalization.NumberFormatInfo)System.Globalization.NumberFormatInfo.CurrentInfo.Clone(); + formatter.CurrencySymbol = string.Empty; + formatter.CurrencyDecimalDigits = 2; + formatter.NumberDecimalDigits = 2; + formatter.PercentDecimalDigits = 2; + audio = AudioPlayer.Audio; + if (audio == null) + Log.Debug("[ScienceAlert]:DraggableOptionsWindow: Failed to find AudioPlayer instance"); + + filterList.Add(new GUIContent(Localizer.Format("#ScienceAlert_button1")));//"Unresearched" + filterList.Add(new GUIContent(Localizer.Format("#ScienceAlert_button2")));//"Not maxed" + filterList.Add(new GUIContent(Localizer.Format("#ScienceAlert_button3")));//"< 50% collected" + filterList.Add(new GUIContent(Localizer.Format("#ScienceAlert_button4")));//"< 90% collected" + + openButton = ResourceUtil.LoadImage("btnOpen.png"); + deleteButton = ResourceUtil.LoadImage("btnDelete.png"); + renameButton = ResourceUtil.LoadImage("btnRename.png"); + saveButton = ResourceUtil.LoadImage("btnSave.png"); + expandButton = ResourceUtil.LoadImage("btnExpand.png"); + collapseButton = Instantiate(expandButton); + ResourceUtil.FlipTexture(collapseButton, true, true); + collapseButton.Compress(false); + expandButton.Compress(false); + + blackPixel.SetPixel(0, 0, Color.black); + blackPixel.Apply(); + blackPixel.filterMode = FilterMode.Bilinear; + whiteLabel = Instantiate(Settings.Skin); + whiteLabel.label.onNormal.textColor = Color.white; + whiteLabel.toggle.onNormal.textColor = Color.white; + whiteLabel.label.onActive.textColor = Color.white; + submenu = OpenPane.None; + Title = Localizer.Format("#ScienceAlert_Optitle");//"ScienceAlert Options" + miniLabelLeft = new GUIStyle(Skin.label) { fontSize = 10 }; + miniLabelLeft.normal.textColor = miniLabelLeft.onNormal.textColor = Color.white; + miniLabelRight = new GUIStyle(miniLabelLeft) { alignment = TextAnchor.MiddleRight }; + miniLabelCenter = new GUIStyle(miniLabelLeft) { alignment = TextAnchor.MiddleCenter }; + Settings.Instance.OnSave += OnAboutToSave; + OnVisibilityChange += OnVisibilityChanged; + GameEvents.onVesselChange.Add(OnVesselChanged); + LoadFrom(Settings.Instance.additional.GetNode("OptionsWindow") ?? new ConfigNode()); + return new Rect(windowRect.x, windowRect.y, 324f, Screen.height / 5 * 3); + } + + protected new void OnDestroy() + { + base.OnDestroy(); + OnVisibilityChange -= OnVisibilityChanged; + GameEvents.onVesselChange.Remove(OnVesselChanged); + } + + private void OnVisibilityChanged(bool tf) + { + if (tf) + { + OnProfileChanged(); + return; + } + if (manager == null) return; + manager.RebuildObserverList(); + } + + public void OnProfileChanged() + { + if (ScienceAlertProfileManager.ActiveProfile == null) return; + thresholdValue = ScienceAlertProfileManager.ActiveProfile.ScienceThreshold.ToString("F2", formatter); + List experimentIDs = ResearchAndDevelopment.GetExperimentIDs(); + IOrderedEnumerable orderedEnumerable = from expid in experimentIDs + orderby ResearchAndDevelopment.GetExperiment(expid).experimentTitle + select expid; + experimentIds.Clear(); + using (IEnumerator enumerator = orderedEnumerable.GetEnumerator()) + { + while (enumerator.MoveNext()) + { + string current = enumerator.Current; + experimentIds.Add(current, (int)System.Convert.ChangeType(ScienceAlertProfileManager.ActiveProfile[current].Filter, + ScienceAlertProfileManager.ActiveProfile[current].Filter.GetTypeCode())); + } + } + } + + private void OnVesselChanged(Vessel vessel) + { + OnVisibilityChanged(Visible); + } + + protected override void OnCloseClick() + { + Visible = false; + } + + private void OnAboutToSave() + { + SaveInto(Settings.Instance.additional.GetNode("OptionsWindow") ?? Settings.Instance.additional.AddNode("OptionsWindow")); + } + + protected override void DrawUI() + { + GUILayout.BeginVertical(GUILayout.ExpandWidth(true), GUILayout.Height(Screen.height / 5 * 3)); + GUILayout.Label(new GUIContent(Localizer.Format("#ScienceAlert_label1")), GUILayout.ExpandWidth(true));//"Global Warp Settings" + Settings.Instance.GlobalWarp = (Settings.WarpSetting)GUILayout.SelectionGrid((int)Settings.Instance.GlobalWarp, new[] + { + new GUIContent(Localizer.Format("#ScienceAlert_button5")),//"By Experiment" + new GUIContent(Localizer.Format("#ScienceAlert_button6")),//"Globally on" + new GUIContent(Localizer.Format("#ScienceAlert_button7"))//"Globally off" + }, 3, GUILayout.ExpandWidth(false)); + GUILayout.Label(new GUIContent(Localizer.Format("#ScienceAlert_label2")), GUILayout.ExpandWidth(true));//"Global Alert Sound" + Settings.Instance.SoundNotification = (Settings.SoundNotifySetting)GUILayout.SelectionGrid((int)Settings.Instance.SoundNotification, new[] + { + new GUIContent(Localizer.Format("#ScienceAlert_button5")),//"By Experiment" + new GUIContent(Localizer.Format("#ScienceAlert_button8")),//"Always" + new GUIContent(Localizer.Format("#ScienceAlert_button9"))//"Never" + }, 3, GUILayout.ExpandWidth(false)); + GUILayout.Space(4f); + GUILayout.BeginHorizontal(); + GUILayout.Label(new GUIContent(Localizer.Format("#ScienceAlert_label3")));//"Additional Options" + GUILayout.FlexibleSpace(); + if (AudibleButton(new GUIContent(submenu == OpenPane.AdditionalOptions ? collapseButton : expandButton))) + { + submenu = submenu == OpenPane.AdditionalOptions ? OpenPane.None : OpenPane.AdditionalOptions; + } + GUILayout.EndHorizontal(); + switch (submenu) + { + case OpenPane.None: + DrawProfileSettings(); + break; + case OpenPane.AdditionalOptions: + DrawAdditionalOptions(); + break; + case OpenPane.LoadProfiles: + DrawProfileList(); + break; + } + GUILayout.EndVertical(); + } + + private void DrawAdditionalOptions() + { + additionalScrollPos = GUILayout.BeginScrollView(additionalScrollPos, GUILayout.ExpandHeight(true)); + GUILayout.Space(4f); + GUILayout.BeginVertical(GUILayout.ExpandHeight(true)); + GUILayout.Box(Localizer.Format("#ScienceAlert_label4"), GUILayout.ExpandWidth(true));//"User Interface Settings" + GUILayout.BeginHorizontal(GUILayout.ExpandWidth(true)); + GUILayout.Label(Localizer.Format("#ScienceAlert_label5"), GUILayout.ExpandWidth(true));//"Globally Enable Animation" + Settings.Instance.FlaskAnimationEnabled = AudibleToggle(Settings.Instance.FlaskAnimationEnabled, string.Empty, null, new[] + { + GUILayout.ExpandWidth(false) + }); + if (!Settings.Instance.FlaskAnimationEnabled && ScienceAlert.Instance.IsAnimating) + { + ScienceAlert.Instance.SetLit(); + } + GUILayout.EndHorizontal(); + Settings.Instance.ShowReportValue = AudibleToggle(Settings.Instance.ShowReportValue, Localizer.Format("#ScienceAlert_toggle1"));//"Display Report Value" + Settings.Instance.DisplayCurrentBiome = AudibleToggle(Settings.Instance.DisplayCurrentBiome, Localizer.Format("#ScienceAlert_toggle2"));//"Display Biome in Experiment List" + Settings.Instance.EvaReportOnTop = AudibleToggle(Settings.Instance.EvaReportOnTop, Localizer.Format("#ScienceAlert_toggle3"));//"List EVA Report first" + GUILayout.Label(Localizer.Format("#ScienceAlert_label6"));//"Window Opacity" + GUILayout.BeginHorizontal(); + GUILayout.Label(Localizer.Format("#ScienceAlert_label7"), miniLabelLeft);//"Less" + GUILayout.FlexibleSpace(); + GUILayout.Label(Localizer.Format("#ScienceAlert_label8"), miniLabelRight);//"More" + GUILayout.EndHorizontal(); + Settings.Instance.WindowOpacity = (int)GUILayout.HorizontalSlider(Settings.Instance.WindowOpacity, 0f, 255f, GUILayout.ExpandWidth(true), GUILayout.MaxHeight(16f)); + GUILayout.Space(8f); + GUILayout.Box(Localizer.Format("#ScienceAlert_label9"), GUILayout.ExpandWidth(true));//"Third-party Integration Options" + GUILayout.BeginHorizontal(GUILayout.ExpandWidth(true)); + Settings.ScanInterface scanInterfaceType = Settings.Instance.ScanInterfaceType; + Color color = GUI.color; + if (!SCANsatInterface.IsAvailable()) + { + GUI.color = Color.red; + } + bool flag = AudibleToggle(Settings.Instance.ScanInterfaceType == Settings.ScanInterface.ScanSat, Localizer.Format("#ScienceAlert_toggle4"), null, new[]//"Enable SCANsat integration" + { + GUILayout.ExpandWidth(true) + }); + GUI.color = color; + if (flag && scanInterfaceType != Settings.ScanInterface.ScanSat && !SCANsatInterface.IsAvailable()) + { + PopupDialog.SpawnPopupDialog(new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f), Localizer.Format("#ScienceAlert_Msg1title") + , Localizer.Format("#ScienceAlert_Msg1"), "", Localizer.Format("#ScienceAlert_Msg1_button1"),//"SCANsat Not Found""SCANsat was not found. You must install SCANsat to use this feature.""Okay" + false, HighLogic.UISkin); + flag = false; + } + Settings.Instance.ScanInterfaceType = flag ? Settings.ScanInterface.ScanSat : Settings.ScanInterface.None; + scienceAlert.ScanInterfaceType = Settings.Instance.ScanInterfaceType; + GUILayout.EndHorizontal(); + GUILayout.BeginHorizontal(GUILayout.ExpandWidth(true)); + //Settings.ToolbarInterface toolbarInterfaceType = Settings.Instance.ToolbarInterfaceType; + Color color2 = GUI.color; + + GUI.color = color2; + + GUILayout.EndHorizontal(); + GUILayout.Box(Localizer.Format("#ScienceAlert_label10"), GUILayout.ExpandWidth(true));//"Crewed Vessel Settings" + bool checkSurfaceSampleNotEva = Settings.Instance.CheckSurfaceSampleNotEva; + Settings.Instance.CheckSurfaceSampleNotEva = AudibleToggle(checkSurfaceSampleNotEva, Localizer.Format("#ScienceAlert_toggle5"));//"Track surface sample in vessel" + if (checkSurfaceSampleNotEva != Settings.Instance.CheckSurfaceSampleNotEva) + { + manager.RebuildObserverList(); + } + GUILayout.EndVertical(); + GUI.skin = Settings.Skin; + GUILayout.EndScrollView(); + } + + private void DrawProfileSettings() + { + if (ScienceAlertProfileManager.HasActiveProfile) + { + GUILayout.BeginHorizontal(); + GUILayout.Box(Localizer.Format("#ScienceAlert_label11", ScienceAlertProfileManager.ActiveProfile.DisplayName), GUILayout.ExpandHeight(true), GUILayout.ExpandWidth(true));//$"Profile: <<1>>{}" + if (AudibleButton(new GUIContent(renameButton), GUILayout.MaxWidth(24f))) + { + SpawnRenamePopup(ScienceAlertProfileManager.ActiveProfile); + } + GUI.enabled = ScienceAlertProfileManager.ActiveProfile.modified; + if (AudibleButton(new GUIContent(saveButton), GUILayout.MaxWidth(24f))) + { + SpawnSavePopup(); + } + GUI.enabled = true; + if (AudibleButton(new GUIContent(openButton), GUILayout.MaxWidth(24f))) + { + submenu = OpenPane.LoadProfiles; + } + GUILayout.EndHorizontal(); + scrollPos = GUILayout.BeginScrollView(scrollPos, Settings.Skin.scrollView); + GUI.skin = Settings.Skin; + GUILayout.Space(4f); + GUI.SetNextControlName("ThresholdHeader"); + GUILayout.Box(Localizer.Format("#ScienceAlert_label12"));//"Alert Threshold" + GUILayout.BeginHorizontal(GUILayout.ExpandWidth(true), GUILayout.MinHeight(14f)); + if (ScienceAlertProfileManager.ActiveProfile.ScienceThreshold > 0f) + { + GUILayout.Label(Localizer.Format("#ScienceAlert_label13", ScienceAlertProfileManager.ActiveProfile.ScienceThreshold.ToString("F2", formatter)));//$"Alert Threshold: <<1>>" + } + else + { + Color color = GUI.color; + GUI.color = XKCDColors.Salmon; + GUILayout.Label(Localizer.Format("#ScienceAlert_label14"));//"(disabled)" + GUI.color = color; + } + GUILayout.FlexibleSpace(); + if (string.IsNullOrEmpty(thresholdValue)) + { + thresholdValue = ScienceAlertProfileManager.ActiveProfile.scienceThreshold.ToString("F2", formatter); + } + GUI.SetNextControlName("ThresholdText"); + string s = GUILayout.TextField(thresholdValue, GUILayout.MinWidth(60f)); + if (Event.current.keyCode == KeyCode.Escape) + { + GUI.FocusControl("ThresholdHeader"); + } + if (GUI.GetNameOfFocusedControl() == "ThresholdText") + { + try + { + float scienceThreshold = float.Parse(s, formatter); + ScienceAlertProfileManager.ActiveProfile.ScienceThreshold = scienceThreshold; + thresholdValue = s; + } + catch (System.Exception) + { + // ignored + } + if (!InputLockManager.IsLocked(ControlTypes.ACTIONS_ALL)) + { + InputLockManager.SetControlLock(ControlTypes.ACTIONS_ALL, "ScienceAlertThreshold"); + } + } + else if (InputLockManager.GetControlLock("ScienceAlertThreshold") != ControlTypes.None) + { + InputLockManager.RemoveControlLock("ScienceAlertThreshold"); + } + GUILayout.EndHorizontal(); + GUILayout.Space(3f); + var num = GUILayout.HorizontalSlider(ScienceAlertProfileManager.ActiveProfile.ScienceThreshold, 0f, 100f, GUILayout.ExpandWidth(true), GUILayout.Height(14f)); + if (num != ScienceAlertProfileManager.ActiveProfile.scienceThreshold) + { + ScienceAlertProfileManager.ActiveProfile.ScienceThreshold = num; + thresholdValue = num.ToString("F2", formatter); + } + GUILayout.BeginHorizontal(GUILayout.ExpandWidth(true), GUILayout.MaxHeight(10f)); + GUILayout.Label("0", miniLabelLeft); + GUILayout.FlexibleSpace(); + GUILayout.Label(Localizer.Format("#ScienceAlert_label15"), miniLabelCenter);//"Science Amount" + GUILayout.FlexibleSpace(); + GUILayout.Label("100", miniLabelRight); + GUILayout.EndHorizontal(); + GUILayout.Space(10f); + List list = new List(experimentIds.Keys); + for (int i = list.Count - 1; i >= 0; i--) + { + string current = list[i]; + + GUILayout.Space(4f); + ExperimentSettings experimentSettings = ScienceAlertProfileManager.ActiveProfile[current]; + string experimentTitle = ResearchAndDevelopment.GetExperiment(current).experimentTitle; + GUILayout.Box(experimentTitle, GUILayout.ExpandWidth(true)); + experimentSettings.Enabled = AudibleToggle(experimentSettings.Enabled, Localizer.Format("#ScienceAlert_toggle6"));//"Enabled" + experimentSettings.AnimationOnDiscovery = AudibleToggle(experimentSettings.AnimationOnDiscovery, Localizer.Format("#ScienceAlert_toggle7"));//"Animation on discovery" + experimentSettings.SoundOnDiscovery = AudibleToggle(experimentSettings.SoundOnDiscovery, Localizer.Format("#ScienceAlert_toggle8"));//"Sound on discovery" + experimentSettings.StopWarpOnDiscovery = AudibleToggle(experimentSettings.StopWarpOnDiscovery, Localizer.Format("#ScienceAlert_toggle9"));//"Stop warp on discovery" + GUILayout.Label(new GUIContent(Localizer.Format("#ScienceAlert_toggle10")), GUILayout.ExpandWidth(true), GUILayout.MinHeight(24f));//"Filter Method" + int num2 = experimentIds[current]; + experimentIds[current] = AudibleSelectionGrid(num2, ref experimentSettings); + } + GUILayout.EndScrollView(); + return; + } + GUI.color = Color.red; + GUILayout.Label(Localizer.Format("#ScienceAlert_label16"));//"No profile active" + } + + private void DrawProfileList() + { + profileScrollPos = GUILayout.BeginScrollView(profileScrollPos, Settings.Skin.scrollView); + bool profilesExist = false; + if (ScienceAlertProfileManager.Count > 0) + { + GUILayout.Label(Localizer.Format("#ScienceAlert_label17"));//"Select a profile to load" + GUILayout.Box(blackPixel, GUILayout.ExpandWidth(true), GUILayout.MinHeight(1f), GUILayout.MaxHeight(3f)); + GUILayout.Space(4f); + Dictionary profiles = ScienceAlertProfileManager.Profiles; + DrawProfileList_ListItem(ScienceAlertProfileManager.DefaultProfile); + + foreach (var current in profiles.Values) + { + if (current != ScienceAlertProfileManager.DefaultProfile) + { + DrawProfileList_ListItem(current); + profilesExist = true; + } + } + + } + if (!profilesExist) + { + GUILayout.FlexibleSpace(); + GUILayout.Box(Localizer.Format("#ScienceAlert_label18"), GUILayout.MinHeight(64f));//"No profiles saved" + GUILayout.FlexibleSpace(); + } + //IL_F1: + GUILayout.Space(10f); + GUILayout.BeginHorizontal(); + GUILayout.FlexibleSpace(); + if (AudibleButton(new GUIContent(Localizer.Format("#ScienceAlert_button10"), "Cancel load operation")))//"Cancel" + { + submenu = OpenPane.None; + } + GUILayout.EndHorizontal(); + GUILayout.EndScrollView(); + } + + private void DrawProfileList_ListItem(Profile profile) + { + GUILayout.BeginHorizontal(); + GUILayout.Box(profile.name, GUILayout.ExpandWidth(true)); + GUI.enabled = profile != ScienceAlertProfileManager.DefaultProfile; + if (AudibleButton(new GUIContent(renameButton), GUILayout.MaxWidth(24f), GUILayout.MinWidth(24f))) + { + SpawnRenamePopup(profile); + } + GUI.enabled = true; + if (AudibleButton(new GUIContent(openButton), GUILayout.MaxWidth(24f), GUILayout.MinWidth(24f))) + { + SpawnOpenPopup(profile); + } + GUI.enabled = profile != ScienceAlertProfileManager.DefaultProfile; + if (AudibleButton(new GUIContent(deleteButton), GUILayout.MaxWidth(24f), GUILayout.MinWidth(24f))) + { + SpawnDeletePopup(profile); + } + GUI.enabled = true; + GUILayout.EndHorizontal(); + } + + private bool AudibleToggle(bool value, string content, GUIStyle style = null, GUILayoutOption[] options = null) + { + return AudibleToggle(value, new GUIContent(content), style, options); + } + + private bool AudibleToggle(bool value, GUIContent content, GUIStyle style = null, GUILayoutOption[] options = null) + { + bool flag = GUILayout.Toggle(value, content, style == null ? Settings.Skin.toggle : style, options); + if (flag != value) + audio.PlayUI("click1"); + return flag; + } + + private int AudibleSelectionGrid(int currentValue, ref ExperimentSettings settings) + { + int num = GUILayout.SelectionGrid(currentValue, filterList.ToArray(), 2, GUILayout.ExpandWidth(true)); + if (num == currentValue) return num; + audio.PlayUI("click1"); + settings.Filter = (ExperimentSettings.FilterMethod)num; + return num; + } + + private bool AudibleButton(GUIContent content, params GUILayoutOption[] options) + { + bool flag = GUILayout.Button(content, options); + if (!flag) return false; + audio.PlayUI("click1"); + return true; + } + + private void SpawnSavePopup() + { + editText = ScienceAlertProfileManager.ActiveProfile.name; + LockControls("ScienceAlertSavePopup"); + + DialogGUIBase[] dialogOptions = new DialogGUIBase[2]; + dialogOptions[0] = new DialogGUIButton(Localizer.Format("#ScienceAlert_button11"), SaveCurrentProfile);//"SAVE" + dialogOptions[1] = new DialogGUIButton(Localizer.Format("#ScienceAlert_button12"), DismissPopup);//"CANCEL" + + popup = PopupDialog.SpawnPopupDialog(new MultiOptionDialog("", "", Localizer.Format("#ScienceAlert_label19", editText),//$"SAVE '{}'?" + HighLogic.UISkin, dialogOptions), + false, HighLogic.UISkin); + } + + private void SaveCurrentProfile() + { + if (popup != null) + popup.Dismiss(); + else + editText = ScienceAlertProfileManager.ActiveProfile.name; + + // Confirm overwrite an existing non-active profile + if (ScienceAlertProfileManager.HaveStoredProfile(editText) && ScienceAlertProfileManager.ActiveProfile.name != editText) + { + popup = PopupDialog.SpawnPopupDialog(new MultiOptionDialog("", "", + Localizer.Format("#ScienceAlert_label20", editText), HighLogic.UISkin,//$"Profile '{}' already exists!" + new DialogGUIButton(Localizer.Format("#ScienceAlert_button13"), SaveCurrentProfileOverwrite),//"Overwrite" + new DialogGUIButton(Localizer.Format("#ScienceAlert_button10"), DismissPopup)),//"Cancel" + false, HighLogic.UISkin); + } + else + SaveCurrentProfileOverwrite(); // save to go ahead and save since no existing profile with this key exists + } + + private void SaveCurrentProfileOverwrite() + { + ScienceAlertProfileManager.StoreActiveProfile(editText); + Settings.Instance.Save(); + DismissPopup(); + } + + private void SpawnRenamePopup(Profile target) + { + editProfile = target; + editText = target.name; + LockControls("ScienceAlertRenamePopup"); + + DialogGUIBase[] dialogOptions = new DialogGUIBase[3]; + dialogOptions[0] = new DialogGUITextInput(editText, false, 22, s => { editText = s; return s; }, 30f); + dialogOptions[1] = new DialogGUIButton(Localizer.Format("#ScienceAlert_button14"), RenameTargetProfile);//"RENAME" + dialogOptions[2] = new DialogGUIButton(Localizer.Format("#ScienceAlert_button10"), DismissPopup);//"CANCEL" + + popup = PopupDialog.SpawnPopupDialog( + new MultiOptionDialog("", "", Localizer.Format("#ScienceAlert_Msg2title", target.name),//$"Rename '{}' to:" + HighLogic.UISkin, dialogOptions), + false, HighLogic.UISkin); + } + + private void RenameTargetProfile() + { + if (editProfile.modified || !ScienceAlertProfileManager.HaveStoredProfile(editProfile.name)) + { + RenameTargetProfileOverwrite(); + } + else + { + if (ScienceAlertProfileManager.HaveStoredProfile(editText)) + { + popup.Dismiss(); + popup = PopupDialog.SpawnPopupDialog( + new MultiOptionDialog(string.Empty, Localizer.Format("#ScienceAlert_Msg2", editText), Localizer.Format("#ScienceAlert_Msg2title2"), HighLogic.UISkin, //$"'{}' already exists. Overwrite?""RenameTargetProfile" + new DialogGUIButton(Localizer.Format("#ScienceAlert_Msg2_button1"), RenameTargetProfileOverwrite),//"Yes" + new DialogGUIButton(Localizer.Format("#ScienceAlert_Msg2_button2"), DismissPopup)),//"No" + false, HighLogic.UISkin); + return; + } + RenameTargetProfileOverwrite(); + } + SpawnSavePopup(); + DismissPopup(); + } + + private void RenameTargetProfileOverwrite() + { + if (!editProfile.modified && ScienceAlertProfileManager.HaveStoredProfile(editProfile.name)) + { + ScienceAlertProfileManager.RenameProfile(editProfile.name, editText); + if (!ScienceAlertProfileManager.ActiveProfile.modified) + { + ScienceAlertProfileManager.ActiveProfile.name = editText; + } + } + else + { + editProfile.name = editText; + editProfile.modified = true; + } + DismissPopup(); + } + + private void SpawnDeletePopup(Profile target) + { + editProfile = target; + LockControls("ScienceAlertDeletePopup"); + popup = PopupDialog.SpawnPopupDialog( + new MultiOptionDialog("", "", Localizer.Format("#ScienceAlert_Msg3", target.name), HighLogic.UISkin, //$"Are you sure you want to\ndelete '{}'?" + new DialogGUIButton(Localizer.Format("#ScienceAlert_button15"), DeleteTargetProfile), //"Confirm" + new DialogGUIButton(Localizer.Format("#ScienceAlert_button10"), DismissPopup)),//"Cancel" + false, HighLogic.UISkin); + } + + private void DeleteTargetProfile() + { + DismissPopup(); + ScienceAlertProfileManager.DeleteProfile(editProfile.name); + } + + private void SpawnOpenPopup(Profile target) + { + editProfile = target; + LockControls("ScienceAlertOpenPopup"); + popup = PopupDialog.SpawnPopupDialog( + new MultiOptionDialog(string.Empty, Localizer.Format("#ScienceAlert_Msg3_2", editProfile.name),//$"Load '{}'?\nUnsaved settings will be lost." + Localizer.Format("#ScienceAlert_Msg3title"), HighLogic.UISkin,//"Science Alert Open Popup" + new DialogGUIButton(Localizer.Format("#ScienceAlert_button15"), LoadTargetProfile),//"Confirm" + new DialogGUIButton(Localizer.Format("#ScienceAlert_button10"), DismissPopup)),//"Cancel" + false, HighLogic.UISkin); + } + + private void LoadTargetProfile() + { + DismissPopup(); + if (!ScienceAlertProfileManager.AssignAsActiveProfile(editProfile.Clone())) return; + submenu = OpenPane.None; + OnVisibilityChanged(Visible); + } + + private void LockControls(string lockName) + { + this.lockName = lockName; + InputLockManager.SetControlLock(ControlTypes.ACTIONS_ALL, lockName); + } + + private void DismissPopup() + { + if (popup) popup.Dismiss(); + InputLockManager.RemoveControlLock(lockName); + lockName = string.Empty; + } + } +} diff --git a/Source/ScienceAlert.Windows/WindowEventLogic.cs b/Source/ScienceAlert.Windows/WindowEventLogic.cs new file mode 100644 index 0000000..7380f5c --- /dev/null +++ b/Source/ScienceAlert.Windows/WindowEventLogic.cs @@ -0,0 +1,64 @@ +using ReeperCommon; +using ScienceAlert.Experiments; + +using UnityEngine; + +namespace ScienceAlert.Windows +{ + /// + /// This class controls which window(s) are shown when + /// + class WindowEventLogic : MonoBehaviour + { + internal static DraggableExperimentList experimentList; + internal static DraggableOptionsWindow optionsWindow; +#if DEBUGWINDOW + internal static DraggableDebugWindow debugWindow; +#endif + internal ScienceAlert scienceAlert; + + private void Awake() + { + Log.Normal("Customizing DraggableWindow"); + + DraggableWindow.CloseTexture = ResourceUtil.LoadImage("btnClose.png"); + DraggableWindow.LockTexture = ResourceUtil.LoadImage("btnLock.png"); + DraggableWindow.UnlockTexture = ResourceUtil.LoadImage("btnUnlock.png"); + DraggableWindow.ButtonHoverBackground = ResourceUtil.LoadImage("btnBackground.png"); + + DraggableWindow.ButtonSound = "click1"; + + scienceAlert = GetComponent(); + + optionsWindow = new GameObject("ScienceAlert.OptionsWindow").AddComponent(); + optionsWindow.scienceAlert = GetComponent(); + optionsWindow.manager = GetComponent(); + + experimentList = new GameObject("ScienceAlert.ExperimentList").AddComponent(); + experimentList.biomeFilter = GetComponent(); + experimentList.manager = GetComponent(); +#if DEBUGWINDOW + debugWindow = new GameObject("ScienceAlert.DebugWindow").AddComponent(); + debugWindow.Visible = false; +#endif + optionsWindow.Visible = experimentList.Visible = false; + } + + private void Start() + { + + //scienceAlert.OnToolbarButtonChanged += OnToolbarChanged; + scienceAlert.OnScanInterfaceChanged += OnInterfaceChanged; +#if DEBUGWINDOW + debugWindow.OnVisibilityChange += OnWindowVisibilityChanged; +#endif + //OnToolbarChanged(); + OnInterfaceChanged(); + } + + private void OnInterfaceChanged() + { + experimentList.scanInterface = GetComponent(); + } + } +} diff --git a/Source/ScienceAlert.csproj b/Source/ScienceAlert.csproj new file mode 100644 index 0000000..5d3ed4e --- /dev/null +++ b/Source/ScienceAlert.csproj @@ -0,0 +1,139 @@ + + + + {835DC165-6942-4C55-A84E-A9DE6B6FA840} + Debug + AnyCPU + Library + ScienceAlert + v4.7.2 + 4 + false + + + + AnyCPU + + + bin\Debug\ + true + full + false + + + bin\Release\ + true + pdbonly + true + + + bin\Debug\ + DEBUG + portable + + + portable + + + + False + + + False + + + $(KSPDIR)\GameData\DMagicOrbitalScience\Plugins\DMagic.dll + + + $(KSPDIR)\GameData\DMagicScienceAnimate\DMModuleScienceAnimateGeneric.dll + + + + $(KSPDIR)\GameData\000_ClickThroughBlocker\Plugins\ClickThroughBlocker.dll + + + + + $(KSPDIR)\GameData\001_ToolbarControl\Plugins\ToolbarControl.dll + + + + + + True + True + AssemblyVersion.tt + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + TextTemplatingFileGenerator + AssemblyVersion.cs + + + + + + + + +set KSPDIR=$(KSPDIR) + +IF "%25KSPDIR%25"=="" ( + + ECHO Configuration error - KSPDIR not specified in project. + + ECHO Either set KSPDIR environment variable or edit BetterLoadSaveGame.Common.props + + PAUSE + + GOTO DONE + +) + +start /D $(SolutionDir) /WAIT deploy.bat $(TargetDir) $(TargetFileName) $(TargetName) + +if $(ConfigurationName) == Release ( + + start /D $(SolutionDir) /WAIT buildRelease.bat $(TargetDir) $(TargetFileName) $(TargetName) + +) + + + "$(DevEnvDir)\texttransform.exe" "$(ProjectDir)AssemblyVersion.tt" + + \ No newline at end of file diff --git a/ScienceAlert.csproj b/Source/ScienceAlert.csproj.173 similarity index 68% rename from ScienceAlert.csproj rename to Source/ScienceAlert.csproj.173 index 10a03c4..1900bdb 100644 --- a/ScienceAlert.csproj +++ b/Source/ScienceAlert.csproj.173 @@ -1,106 +1,142 @@ - - - - {835DC165-6942-4C55-A84E-A9DE6B6FA840} - Debug - AnyCPU - Library - ScienceAlert - v3.5 - 4 - - - - AnyCPU - - - bin\Debug\ - true - full - false - - - bin\Release\ - true - pdbonly - true - - - D:\KSP_131_test\GameData\ScienceAlert\ - - - - D:\KSP_131_test\KSP_x64_Data\Managed\Assembly-CSharp.dll - False - - - - - D:\KSP_131_test\KSP_x64_Data\Managed\UnityEngine.dll - False - - - D:\KSP_131_test\KSP_x64_Data\Managed\UnityEngine.UI.dll - False - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - "D:\Microsoft Visual Studio 14.0\pdb2mdb\pdb2mdb.exe" "$(TargetPath)" - + + + + {835DC165-6942-4C55-A84E-A9DE6B6FA840} + Debug + AnyCPU + Library + ScienceAlert + v3.5 + 4 + + + + AnyCPU + + + bin\Debug\ + true + full + false + + + bin\Release\ + true + pdbonly + true + + + bin\Debug\ + DEBUG + + + + R:\KSP_1.7.3_dev\KSP_x64_Data\Managed\Assembly-CSharp.dll + False + + + R:\KSP_1.7.3_dev\GameData\000_ClickThroughBlocker\Plugins\ClickThroughBlocker.dll + + + + + R:\KSP_1.7.3_dev\GameData\001_ToolbarControl\Plugins\ToolbarControl.dll + + + R:\KSP_1.7.3_dev\KSP_x64_Data\Managed\UnityEngine.dll + False + + + R:\KSP_1.7.3_dev\KSP_x64_Data\Managed\UnityEngine.UI.dll + False + + + + + True + True + AssemblyVersion.tt + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + TextTemplatingFileGenerator + AssemblyVersion.cs + + + + + + + + "..\..\..\..\pdb2mdb.exe" "$(TargetPath)" + + + +start /D D:\Users\jbb\github\ScienceAlert /WAIT deploy.bat $(TargetDir) $(TargetFileName) + + + +if $(ConfigurationName) == Release ( + + + start /D D:\Users\jbb\github\ScienceAlert /WAIT buildRelease.bat $(TargetDir) $(TargetFileName) + + +) + + + + +set textTemplatingPath="%25ProgramFiles(x86)%25\Microsoft Visual Studio\2017\Community\Common7\IDE\texttransform.exe" + +%25textTemplatingPath%25 "$(ProjectDir)AssemblyVersion.tt" + + \ No newline at end of file diff --git a/Source/ScienceAlert/ExcludeFilters.cs b/Source/ScienceAlert/ExcludeFilters.cs new file mode 100644 index 0000000..a5b5e78 --- /dev/null +++ b/Source/ScienceAlert/ExcludeFilters.cs @@ -0,0 +1,74 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using ReeperCommon; + +using System.Linq; + + +namespace ScienceAlert +{ + class ExcludeFilters + { + + internal static string[] excludedExperiments = null; + internal static string[] excludedManufacturers = null; + + internal static bool IsExcluded(ModuleScienceExperiment exp) + { + bool b1 = excludedManufacturers.Contains(exp.part.partInfo.manufacturer); + bool b2 = excludedExperiments.Contains(exp.experimentID); + return b1 | b2; + } + + public ExcludeFilters() + { + if (excludedExperiments == null) + { + List expList = new List(); + ConfigNode[] excludedNode = GameDatabase.Instance.GetConfigNodes("KEI_EXCLUDED_EXPERIMENTS"); + + if (excludedNode != null) + { + for (int i = excludedNode.Length - 1; i >= 0; i--) + { + string[] types = excludedNode[i].GetValues("experiment"); + expList.AddRange(types); + } + } + else + Log.Error("Missing config file"); + + excludedExperiments = expList.Distinct().ToArray(); + +#if DEBUG + foreach (var s in excludedExperiments) + Log.Info("Excluded experiment: " + s); +#endif + } + + if (excludedManufacturers == null) + { + List expList = new List(); + ConfigNode[] excludedNode = GameDatabase.Instance.GetConfigNodes("KEI_EXCLUDED_MANUFACTURERS"); + if (excludedNode != null) + { + for (int i = excludedNode.Length - 1; i >= 0; i--) + { + string[] types = excludedNode[i].GetValues("manufacturer"); + expList.AddRange(types); + } + } + else + Log.Error("Missing config file"); + + excludedManufacturers = expList.Distinct().ToArray(); +#if DEBUG + foreach (var s in excludedManufacturers) + Log.Info("Excluded manufacturer: " + s); +#endif + } + + } + } +} diff --git a/Source/ScienceAlert/Interfaces.cs b/Source/ScienceAlert/Interfaces.cs new file mode 100644 index 0000000..8db17c9 --- /dev/null +++ b/Source/ScienceAlert/Interfaces.cs @@ -0,0 +1,16 @@ +using UnityEngine; + +namespace ScienceAlert +{ + public class DefaultScanInterface : ScanInterface + { + } + + public class ScanInterface : MonoBehaviour + { + public virtual bool HaveScanData(double lat, double lon, CelestialBody body) + { + return true; + } + } +} diff --git a/Source/ScienceAlert/MagicDataTransmitter.cs b/Source/ScienceAlert/MagicDataTransmitter.cs new file mode 100644 index 0000000..16c54d3 --- /dev/null +++ b/Source/ScienceAlert/MagicDataTransmitter.cs @@ -0,0 +1,238 @@ +using ReeperCommon; +using System.Collections.Generic; +using System.Linq; + +namespace ScienceAlert +{ + public class NonexistentTransmitterException : System.Exception + { + } + + public class MagicDataTransmitter : PartModule, IScienceDataTransmitter + { + private Dictionary, Callback>> realTransmitters = new Dictionary, Callback>>(); + private Dictionary, Callback>>> toBeTransmitted = new Dictionary, Callback>>>(); + internal StorageCache cacheOwner; + + float IScienceDataTransmitter.DataRate => 3.40282347E+38f; + + double IScienceDataTransmitter.DataResourceCost => 0.0; + + public List QueuedData + { + get + { + List list = new List(); + bool flag = false; + try + { + foreach (KeyValuePair, Callback>> current in realTransmitters) + { + if (current.Key == null) + { + Log.Debug("[ScienceAlert]:MagicDataTransmitter: Encountered a bad transmitter value."); + flag = true; + } + else + { + if (!current.Key.IsBusy() && current.Value.Key != null) + { + current.Value.Key.Clear(); + } + if (current.Value.Key != null) + { + list.AddRange(current.Value.Key); + } + list.AddRange(toBeTransmitted[current.Key].SelectMany(transmitterData => transmitterData.Key)); + } + } + } + catch (System.Exception ex) + { + flag = true; + Log.Debug("[ScienceAlert]:Exception occurred in MagicDataTransmitter.QueuedData: {0}", ex); + } + if (flag) + { + Log.Warning("Resetting MagicDataTransmitter due to bad transmitter key or value"); + cacheOwner.ScheduleRebuild(); + } + return list; + } + } + + public void Start() + { + Log.Debug("ALERT:MagicDataTransmitter started"); + RefreshTransmitterQueues(new List, Callback>>()); + } + + public List, Callback>> GetQueuedData() + { + return toBeTransmitted.Values.SelectMany(q => q.ToArray()).ToList(); + } + + public void RefreshTransmitterQueues(List, Callback>> queuedData) + { + if (queuedData == null) + { + throw new System.ArgumentNullException("queuedData"); + } + Dictionary, Callback>> dictionary = new Dictionary, Callback>>(realTransmitters); + realTransmitters.Clear(); + toBeTransmitted.Clear(); + List list = (from tx in FlightGlobals.ActiveVessel.FindPartModulesImplementing() + where !(tx is MagicDataTransmitter) + select tx).ToList(); + if (!list.Any()) + { + Destroy(this); + cacheOwner.ScheduleRebuild(); + } + for (int i = list.Count - 1; i >=0; i--) + { + IScienceDataTransmitter current = list[i]; + + realTransmitters.Add(current, default(KeyValuePair, Callback>)); + toBeTransmitted.Add(current, new Queue, Callback>>()); + } + Log.Debug("ALERT:MagicDataTransmitter has found {0} useable transmitters", list.Count); + foreach (IScienceDataTransmitter current2 in dictionary.Keys) + { + if (realTransmitters.ContainsKey(current2)) + { + realTransmitters[current2] = dictionary[current2]; + } + } + if (!queuedData.Any()) return; + foreach (KeyValuePair, Callback> current3 in queuedData) + { + TransmitData(current3.Key, current3.Value); + } + } + + private void BeginTransmissionWithRealTransmitter(IScienceDataTransmitter transmitter, List science, Callback callback) + { + if (transmitter == null) + { + throw new System.ArgumentNullException("transmitter"); + } + if (science == null) + { + throw new System.ArgumentNullException("science"); + } + if ((PartModule)transmitter == null) + { + TransmitData(science, callback); + throw new NonexistentTransmitterException(); + } + Log.Debug(string.Concat("Beginning real transmission of ", science.Count, " science reports on transmitter ", ((PartModule)transmitter).part.flightID)); + if (callback != null) return; + transmitter.TransmitData(science); + } + + public void Update() + { + Dictionary, Callback>>>.KeyCollection keys = toBeTransmitted.Keys; + try + { + foreach (IScienceDataTransmitter current in keys) + { + if (toBeTransmitted[current].Count > 0 && !current.IsBusy() && current.CanTransmit()) + { + KeyValuePair, Callback> value = toBeTransmitted[current].Dequeue(); + Log.Debug("ALERT:Dispatching " + value.Key.Count + " science data entries to transmitter"); + realTransmitters[current] = value; + BeginTransmissionWithRealTransmitter(current, value.Key, value.Value); + } + } + } + catch (NonexistentTransmitterException) + { + Log.Warning("MagicDataTransmitter: Nonexistent transmitter encountered. Rescanning vessel and re-queuing transmissions"); + realTransmitters.Clear(); + RefreshTransmitterQueues(GetQueuedData()); + if (!realTransmitters.Any()) + { + Log.Warning("MagicDataTransmitter: No real transmitters found. Data will stay queued. If the vessel is switched or scenes are changed before it is dispatched, it will be lost."); + } + } + catch (KeyNotFoundException) + { + Log.Debug("[ScienceAlert]:MagicDataTransmitter appears to be out of date. Any queued data might have been lost."); + toBeTransmitted.Clear(); + realTransmitters.Clear(); + cacheOwner.ScheduleRebuild(); + } + } + + public override void OnSave(ConfigNode node) + { + node.ClearData(); + } + + public override void OnLoad(ConfigNode node) + { + } + + private void QueueTransmission(List data, IScienceDataTransmitter transmitter, Callback callback) + { + if (data.Count == 0) + { + return; + } + Log.Debug("ALERT:Queued " + data.Count + " science reports for transmission"); + toBeTransmitted[transmitter].Enqueue(new KeyValuePair, Callback>(new List(data), callback)); + } + + void IScienceDataTransmitter.TransmitData(List data) + { + TransmitData(data, null); + } + + public void TransmitData(List dataQueue, Callback callback) + { + Log.Debug("ALERT:MagicTransmitter: received {0} ScienceData entries", dataQueue.Count); + Log.Debug(callback == null ? "ALERT: with no callback" : "ALERT:With callback"); + List list = new List(); + foreach (KeyValuePair, Callback>> current in realTransmitters) + { + list.Add(current.Key); + } + if (list.Any()) + { + list = (from potential in list + orderby ScienceUtil.GetTransmitterScore(potential) + select potential).ToList(); + QueueTransmission(dataQueue, list.First(), callback); + return; + } + Log.Debug("[ScienceAlert]:MagicDataTransmitter: Did not find any real transmitters"); + } + + bool IScienceDataTransmitter.IsBusy() + { + return false; + } + + bool IScienceDataTransmitter.CanTransmit() + { + return realTransmitters.Any(pair => pair.Key.CanTransmit()); + } + + private void TransmissionComplete(IScienceDataTransmitter transmitter, Callback original) + { + Log.Debug("ALERT:Received TransmissionComplete callback from " + transmitter.GetType().Name); + if (original != null) + { + original(); + } + } + + public override string ToString() + { + return + $"MagicDataTransmitter attached to {FlightGlobals.ActiveVessel.rootPart.name}; {QueuedData.Count} entries in queue"; + } + } +} diff --git a/Source/ScienceAlert/SCANsatInterface.cs b/Source/ScienceAlert/SCANsatInterface.cs new file mode 100644 index 0000000..b551693 --- /dev/null +++ b/Source/ScienceAlert/SCANsatInterface.cs @@ -0,0 +1,64 @@ +using ReeperCommon; +using System.Linq; + +namespace ScienceAlert +{ + internal class SCANsatInterface : ScanInterface + { + private delegate bool IsCoveredDelegate(double lat, double lon, CelestialBody body, int mask); + private const string SCANutilTypeName = "SCANsat.SCANUtil"; + private static IsCoveredDelegate _isCovered = (double lat, double lon, CelestialBody body, int mask) => true; + private static System.Reflection.MethodInfo _method; + private static bool _ran; + + private void OnDestroy() + { + _ran = false; + } + + public override bool HaveScanData(double lat, double lon, CelestialBody body) + { + return _isCovered(lat, lon, body, 8); + } + + public static bool IsAvailable() + { + if (_method != null && _isCovered != null) return true; + if (_ran) return false; + _ran = true; + try + { + System.Type type = AssemblyLoader.loadedAssemblies.SelectMany((AssemblyLoader.LoadedAssembly loaded) => loaded.assembly.GetExportedTypes()).SingleOrDefault((System.Type t) => t.FullName == "SCANsat.SCANUtil"); + bool result; + if (type == null) + { + result = false; + return result; + } + _method = type.GetMethod("isCovered", new System.Type[] + { + typeof(double), + typeof(double), + typeof(CelestialBody), + typeof(int) + }); + if (_method == null) + { + result = false; + return result; + } + _isCovered = (IsCoveredDelegate)System.Delegate.CreateDelegate(typeof(IsCoveredDelegate), _method); + Log.Debug(_isCovered == null + ? "[ScienceAlert]:SCANsatInterface: Failed to create method delegate" + : "[ScienceAlert]:SCANsatInterface: Interface available"); + result = _isCovered != null; + return result; + } + catch (System.Exception ex) + { + Log.Debug("[ScienceAlert]:Exception in SCANsatInterface.IsAvailable: {0}", ex); + } + return false; + } + } +} diff --git a/Source/ScienceAlert/ScienceAlert.cs b/Source/ScienceAlert/ScienceAlert.cs new file mode 100644 index 0000000..8e88fdf --- /dev/null +++ b/Source/ScienceAlert/ScienceAlert.cs @@ -0,0 +1,388 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using ReeperCommon; +using ScienceAlert.Experiments; +using ScienceAlert.ProfileData; +using ScienceAlert.Windows; +using UnityEngine; +using KSP.UI.Screens; + +using ToolbarControl_NS; + +using System.Linq; + +namespace ScienceAlert +{ + [KSPAddon(KSPAddon.Startup.MainMenu, true)] + public class RegisterToolbar : MonoBehaviour + { + void Start() + { + ToolbarControl.RegisterMod(ScienceAlert.MODID, ScienceAlert.MODNAME); + } + } + + + + [KSPAddon(KSPAddon.Startup.Flight, false)] + public class ScienceAlert : MonoBehaviour + { + internal ToolbarControl toolbarControl; + private ScanInterface scanInterface; + public DraggableOptionsWindow optionsWindow; + public static ScienceAlert Instance = null; + internal ExcludeFilters excludeFilters; + + private Settings.ScanInterface scanInterfaceType; + public event Callback OnScanInterfaceChanged = delegate { }; + public event Callback OnToolbarButtonChanged = delegate { }; + + internal const string MODID = "ScienceAlert_NS"; + internal const string MODNAME = "Science Alert"; + + private const string BlizzyNormalFlaskTexture = "ScienceAlert/PluginData/Textures/flask"; + private const string StockNormalFlaskTexture = "ScienceAlert/PluginData/Textures/flask-38"; + private int FrameCount = 100; + private List StarFlaskTextures = new List(); + private List StarFlaskTextures38 = new List(); + + void CreateButton() + { + if (toolbarControl == null) + { + Log.Write("ExperimentManager.CreateButton", Log.LEVEL.INFO); + + toolbarControl = gameObject.AddComponent(); + toolbarControl.AddToAllToolbars(ButtonLeftClicked, ButtonLeftClicked, + ApplicationLauncher.AppScenes.FLIGHT | ApplicationLauncher.AppScenes.MAPVIEW, + MODID, + "saButton", + StockNormalFlaskTexture, + BlizzyNormalFlaskTexture, + "Left-click to view alert experiments; Right-click for settings" + ); + toolbarControl.AddLeftRightClickCallbacks(null, ButtonRightClicked); + } + } + + private void SliceAtlasTexture() + { + Func GetFrame = delegate (int frame, int desiredLen) + { + string str = frame.ToString(); + while (str.Length < desiredLen) + str = "0" + str; + return str; + }; + + // load textures + try + { + if (!GameDatabase.Instance.ExistsTexture(BlizzyNormalFlaskTexture)) + { + // load normal flask texture + Log.Debug("Loading normal flask texture"); + + Texture2D nflask = ResourceUtil.LoadImage("flask.png"); + if (nflask == null) + { + Log.Error("Failed to create normal flask texture!"); + } + else + { +#if GAMEDATABASE + GameDatabase.TextureInfo ti = new GameDatabase.TextureInfo(null, nflask, false, true, true); + ti.name = BlizzyNormalFlaskTexture; + GameDatabase.Instance.databaseTexture.Add(ti); + // Log.Debug("Created normal flask texture {0}", ti.name); +#endif + } + // + // Load textures for animation here + // + Texture2D sheet = ResourceUtil.LoadImage("sheet.png"); + if (!ToolbarControl.LoadImageFromFile(ref sheet, KSPUtil.ApplicationRootPath + "GameData/ScienceAlert/PluginData/Textures/sheet.png")) + { + Log.Error("Unable to ToolbarControl.LoadImageFromFile for sheet.png, falling back to ResourceUtil.LoadImage"); + } + + + if (sheet == null) + { + Log.Error("Failed to create sprite sheet texture!"); + } + else + { + var rt = RenderTexture.GetTemporary(sheet.width, sheet.height); + var oldRt = RenderTexture.active; + int invertHeight = ((FrameCount - 1) / (sheet.width / 24)) * 24; + + Graphics.Blit(sheet, rt); + RenderTexture.active = rt; + + for (int i = 0; i < FrameCount; ++i) + { + StarFlaskTextures.Add(BlizzyNormalFlaskTexture + GetFrame(i + 1, 4)); + Texture2D sliced = new Texture2D(24, 24, TextureFormat.ARGB32, false); + + sliced.ReadPixels(new Rect((i % (sheet.width / 24)) * 24, /*invertHeight -*/ (i / (sheet.width / 24)) * 24, 24, 24), 0, 0); + sliced.Apply(); + + GameDatabase.TextureInfo ti = new GameDatabase.TextureInfo(null, sliced, false, false, true); + + ti.name = StarFlaskTextures.Last(); + GameDatabase.Instance.databaseTexture.Add(ti); + + // Log.Debug("Added sheet texture {0}", ti.name); + } + + RenderTexture.active = oldRt; + RenderTexture.ReleaseTemporary(rt); + } + + sheet = ResourceUtil.LoadImage("sheet-38.png"); + + if (!ToolbarControl.LoadImageFromFile(ref sheet, KSPUtil.ApplicationRootPath + "GameData/ScienceAlert/PluginData/Textures/sheet-38.png")) + { + Log.Error("Unable to ToolbarControl.LoadImageFromFile for sheet-38, falling back to ResourceUtil.LoadImage"); + } + + if (sheet == null) + { + Log.Error("Failed to create sprite sheet texture!"); + } + else + { + var rt = RenderTexture.GetTemporary(sheet.width, sheet.height); + var oldRt = RenderTexture.active; + int invertHeight = ((FrameCount - 1) / (sheet.width / 38)) * 38; + + Graphics.Blit(sheet, rt); + RenderTexture.active = rt; + + for (int i = 0; i < FrameCount; ++i) + { + StarFlaskTextures38.Add(BlizzyNormalFlaskTexture + "-38-" + GetFrame(i + 1, 4)); + Texture2D sliced = new Texture2D(38, 38, TextureFormat.ARGB32, false); + + sliced.ReadPixels(new Rect((i % (sheet.width / 38)) * 38, /*invertHeight -*/ (i / (sheet.width / 38)) * 38, 38, 38), 0, 0); + sliced.Apply(); + + GameDatabase.TextureInfo ti = new GameDatabase.TextureInfo(null, sliced, false, false, true); + ti.name = StarFlaskTextures38.Last(); + ti.texture = sliced; + GameDatabase.Instance.databaseTexture.Add(ti); + // Log.Debug("Added sheet texture {0}", ti.name); + + } + + RenderTexture.active = oldRt; + RenderTexture.ReleaseTemporary(rt); + } + Log.Debug("Finished loading sprite sheet-38 textures."); + } + else + { // textures already loaded + for (int i = 0; i < FrameCount; ++i) + { + StarFlaskTextures.Add(BlizzyNormalFlaskTexture + GetFrame(i + 1, 4)); + StarFlaskTextures38.Add(BlizzyNormalFlaskTexture + "-38-" + GetFrame(i + 1, 4)); + } + } + } + catch (Exception e) + { + Log.Error("Failed to load textures: {0}", e); + } + } + + void ButtonLeftClicked() + { + WindowEventLogic.experimentList.Visible = !WindowEventLogic.experimentList.Visible; + DraggableExperimentList.Instance.CheckForCollection(); + } + + void ButtonRightClicked() + { + WindowEventLogic.optionsWindow.Visible = !WindowEventLogic.optionsWindow.Visible; + } + + IEnumerator animation; + string TexturePath; + public bool IsAnimating => animation != null; + public bool IsLit => animation == null && TexturePath != BlizzyNormalFlaskTexture; + private float FrameRate = 24f; + private int CurrentFrame = 0; + + internal void PlayAnimation() + { + Log.Write("PlayAnimation", Log.LEVEL.INFO); + if (animation == null) animation = DoAnimation(); + //StartCoroutine(DoAnimation()); + } + /// + /// Is called by Update whenever animation exists to + /// update animation frame. + /// + /// Note: I didn't make this into an actual coroutine + /// because StopCoroutine seems to sometimes throw + /// exceptions + /// + /// + IEnumerator DoAnimation() + { + float elapsed = 0f; + while (true) + { + while (elapsed < 1f / FrameRate) + { + elapsed += Time.deltaTime; + yield return new WaitForSeconds(1f / FrameRate); + } + elapsed -= 1f / FrameRate; + CurrentFrame = (CurrentFrame + 1) % FrameCount; + TexturePath = StarFlaskTextures[CurrentFrame]; + toolbarControl.SetTexture(StarFlaskTextures38[CurrentFrame], StarFlaskTextures[CurrentFrame]); + } + } + internal void StopAnimation() + { + Log.Write("StopAnimation", Log.LEVEL.INFO); + animation = null; + //StopCoroutine(DoAnimation()); + } + /// + /// Switch to normal flask texture + /// + public void SetUnlit() + { + //Log.Write("SetUnlit", Log.LEVEL.INFO); + animation = null; + TexturePath = BlizzyNormalFlaskTexture; + toolbarControl.SetTexture(StockNormalFlaskTexture, BlizzyNormalFlaskTexture); + } + + public void SetLit() + { + //Log.Write("SetLit", Log.LEVEL.INFO); + animation = null; + TexturePath = StarFlaskTextures[0]; + toolbarControl.SetTexture(StarFlaskTextures38[0], StarFlaskTextures[0]); + } + + private void Update() + { + + if (animation != null) + { + animation.MoveNext(); + } + } + + public Settings.ScanInterface ScanInterfaceType + { + get + { + return scanInterfaceType; + } + set + { + if (value == scanInterfaceType && scanInterface != null) return; + if (scanInterface != null) + { + DestroyImmediate(GetComponent()); + } + try + { + switch (value) + { + case Settings.ScanInterface.None: + scanInterface = gameObject.AddComponent(); + break; + case Settings.ScanInterface.ScanSat: + if (!SCANsatInterface.IsAvailable()) + { + ScanInterfaceType = Settings.ScanInterface.None; + return; + } + scanInterface = gameObject.AddComponent(); + break; + default: + throw new NotImplementedException("Unrecognized interface type"); + } + } + catch (Exception ex) + { + Log.Debug("[ScienceAlert]:ScienceAlert.ScanInterfaceType failed with exception {0}", ex); + ScanInterfaceType = Settings.ScanInterface.None; + return; + } + scanInterfaceType = value; + OnScanInterfaceChanged(); + } + } + + void Start() + { + StartCoroutine(DoStart()); + } + private IEnumerator DoStart() + { + while (ResearchAndDevelopment.Instance == null) + { + yield return 0; + } + while (FlightGlobals.ActiveVessel == null) + { + yield return 0; + } + while (!FlightGlobals.ready) + { + yield return 0; + } + Instance = this; + + while (ScienceAlertProfileManager.Instance == null || !ScienceAlertProfileManager.Instance.Ready) + { + yield return 0; + } + + try + { + ScienceExperiment experiment = ResearchAndDevelopment.GetExperiment("asteroidSample"); + if (experiment != null) + { + experiment.experimentTitle = "Sample (Asteroid)"; + } + } + catch (KeyNotFoundException) + { + Destroy(this); + } + DMagicFactory.InitDMagicFactory(); + + + gameObject.AddComponent().LoadSoundsFrom(ConfigUtil.GetDllDirectoryPath() + "/../sounds"); + gameObject.AddComponent(); + gameObject.AddComponent(); + gameObject.AddComponent(); + excludeFilters = new ExcludeFilters(); + ScanInterfaceType = Settings.Instance.ScanInterfaceType; + + SliceAtlasTexture(); + CreateButton(); + } + + public void OnDestroy() + { + if (toolbarControl != null) + { + toolbarControl.OnDestroy(); + Destroy(toolbarControl); + } + Settings.Instance.Save(); + Instance = null; + } + } +} diff --git a/Source/ScienceAlert/Settings.cs b/Source/ScienceAlert/Settings.cs new file mode 100644 index 0000000..3237e49 --- /dev/null +++ b/Source/ScienceAlert/Settings.cs @@ -0,0 +1,243 @@ +using System; +using System.IO; +using ReeperCommon; +using UnityEngine; +using Object = UnityEngine.Object; + +namespace ScienceAlert +{ + public class Settings + { + public delegate void SaveCallback(ConfigNode node); + + public delegate void Callback(); + + public enum WarpSetting + { + ByExperiment, + GlobalOn, + GlobalOff + } + + public enum SoundNotifySetting + { + ByExperiment, + Always, + Never + } + + public enum ScanInterface + { + None, + ScanSat + } + + + private static Settings instance; + + [DoNotSerialize] + private readonly string ConfigPath = ConfigUtil.GetDllDirectoryPath() + "/../PluginData/settings.cfg"; + + [DoNotSerialize] + private GUISkin skin; + + public ConfigNode additional = new ConfigNode("config"); + + [DoNotSerialize] + private int windowOpacity = 255; + + [DoNotSerialize] + protected ScanInterface Interface; + + [DoNotSerialize] + public event Callback OnSave = delegate {}; + + [DoNotSerialize] + public event SaveCallback OnLoad = delegate {}; + + public static Settings Instance + { + get + { + if (instance != null) return instance; + instance = new Settings(); + return instance; + } + } + + public static GUISkin Skin => Instance.skin; + + public bool DebugMode {get;private set;} + + [Subsection("General")] + public WarpSetting GlobalWarp {get;set;} + + public float TimeWarpCheckThreshold {get;private set;} + + [Subsection("General")] + public SoundNotifySetting SoundNotification {get;set;} + + [Subsection("General")] + public double EvaAtmospherePressureWarnThreshold {get;private set;} + + [Subsection("General")] + public float EvaAtmosphereVelocityWarnThreshold {get; private set;} + + [Subsection("UserInterface")] public bool ShowReportValue {get;set;} + + [Subsection("UserInterface")] public bool DisplayCurrentBiome {get;set;} + + [Subsection("UserInterface")] + public bool FlaskAnimationEnabled {get;set;} + + [Subsection("UserInterface")] public float StarFlaskFrameRate {get;private set;} + + public int WindowOpacity + { + get + { + return windowOpacity; + } + set + { + Texture2D background = skin.window.normal.background; + windowOpacity = value; + if (background != null) + { + Color32[] pixels = background.GetPixels32(); + for (int i = 0; i < pixels.Length; i++) + { + pixels[i].a = (byte)Mathf.Clamp(windowOpacity, 0, 255); + } + background.SetPixels32(pixels); + background.Apply(); + } + } + } + + public bool EvaReportOnTop {get;set;} + + [Subsection("CrewedVesselSettings")] + public bool CheckSurfaceSampleNotEva {get;set;} + + public ScanInterface ScanInterfaceType + { + get + { + ScanInterface @interface = Interface; + if (@interface != ScanInterface.ScanSat) + { + return Interface; + } + if (SCANsatInterface.IsAvailable()) + { + return Interface; + } + return ScanInterface.None; + } + set + { + Interface = value; + } + } + + private Settings() + { + skin = Object.Instantiate(HighLogic.Skin); + skin.button = new GUIStyle(skin.button); + skin.button.fixedHeight = 24f; + skin.button.padding = new RectOffset + { + left = 2, + right = 2, + top = 0, + bottom = 0 + }; + skin.button.border = new RectOffset + { + left = 2, + right = 2, + top = 1, + bottom = 1 + }; + skin.toggle.border.top = skin.toggle.border.bottom = skin.toggle.border.left = skin.toggle.border.right = 0; + skin.toggle.margin = new RectOffset(5, 0, 0, 0); + skin.toggle.padding = new RectOffset + { + left = 5, + top = 3, + right = 3, + bottom = 3 + }; + skin.box.alignment = TextAnchor.MiddleCenter; + skin.box.padding = new RectOffset(2, 2, 8, 5); + skin.box.contentOffset = new Vector2(0f, 0f); + skin.horizontalSlider.margin = new RectOffset(); + skin.window = new GUIStyle(skin.GetStyle("window")); + + // Following was causing setfaults in KSP 1.8, when changing the WindowOpacity on the 2nd time in flight scene + //skin.window.onActive.background = skin.window.onFocused.background = skin.window.onNormal.background = skin.window.onHover.background = skin.window.active.background = skin.window.focused.background = skin.window.hover.background = skin.window.normal.background = skin.window.normal.background.CreateReadable(); + + WindowOpacity = 255; + skin.window.onNormal.textColor = skin.window.normal.textColor = XKCDColors.Green_Yellow; + skin.window.onHover.textColor = skin.window.hover.textColor = XKCDColors.YellowishOrange; + skin.window.onFocused.textColor = skin.window.focused.textColor = Color.red; + skin.window.onActive.textColor = skin.window.active.textColor = Color.blue; + skin.window.fontSize = 12; + EvaAtmospherePressureWarnThreshold = 0.00035; + EvaAtmosphereVelocityWarnThreshold = 30f; + ScanInterfaceType = ScanInterface.None; + ShowReportValue = false; + EvaReportOnTop = false; + CheckSurfaceSampleNotEva = false; + DisplayCurrentBiome = false; + StarFlaskFrameRate = 24f; + FlaskAnimationEnabled = true; + TimeWarpCheckThreshold = 5f; + DraggableWindow.DefaultSkin = skin; + Load(); + } + + public void Load() + { + Log.Debug("[ScienceAlert]:Loading settings from {0}", ConfigPath); + if (!File.Exists(ConfigPath)) + { + Log.Debug("[ScienceAlert]:Failed to find settings file {0}", ConfigPath); + Save(); + return; + } + ConfigNode configNode = ConfigNode.Load(ConfigPath); + if (configNode == null) + { + Log.Debug("[ScienceAlert]:Failed to load {0}", ConfigPath); + return; + } + configNode.CreateObjectFromConfigEx(this); +#if OLDLOG + Log.LoadFrom(configNode); +#endif + OnLoad(additional); + } + + public void Save() + { + ConfigNode configNode = null; + try + { + OnSave(); + configNode = this.CreateConfigFromObjectEx() ?? new ConfigNode(); + } + catch (Exception ex) + { + Log.Debug("[ScienceAlert]:Exception while creating ConfigNode from settings: {0}", ex); + } +#if OLDLOG + Log.SaveInto(configNode); +#endif + if (configNode.CountNodes <= 0 && configNode.CountValues <= 0) return; + Log.Debug("[ScienceAlert]:Saving settings to {0}", ConfigPath); + configNode.Save(ConfigPath); + } + } +} diff --git a/Source/ScienceAlert/StorageCache.cs b/Source/ScienceAlert/StorageCache.cs new file mode 100644 index 0000000..ec53074 --- /dev/null +++ b/Source/ScienceAlert/StorageCache.cs @@ -0,0 +1,213 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using ReeperCommon; +using System.Linq; +using UnityEngine; + +namespace ScienceAlert +{ + public class StorageCache : MonoBehaviour + { + protected List storage = new List(); + protected List ret_storage = new List(); + protected List allVessels = FlightGlobals.Vessels; + protected MagicDataTransmitter magicTransmitter; + protected Vessel vessel; + + public int StorageContainerCount => storage.Count; + + public bool IsBusy + { + get; + private set; + } + + public void Start() + { + GameEvents.onVesselChange.Add(OnVesselChange); + GameEvents.onVesselWasModified.Add(OnVesselModified); + GameEvents.onVesselDestroy.Add(OnVesselDestroyed); + vessel = FlightGlobals.ActiveVessel; + ScheduleRebuild(); + } + + public void OnDestroy() + { + GameEvents.onVesselDestroy.Remove(OnVesselDestroyed); + GameEvents.onVesselWasModified.Remove(OnVesselModified); + GameEvents.onVesselChange.Remove(OnVesselChange); + RemoveMagicTransmitter(false); + Log.Debug("ALERT:StorageCache destroyed"); + } + + public void OnVesselChange(Vessel v) + { + RemoveMagicTransmitter(); + vessel = v; + ScheduleRebuild(); + } + + public void OnVesselModified(Vessel v) + { + if (vessel != v) + { + OnVesselChange(v); + return; + } + ScheduleRebuild(); + } + + public void OnVesselDestroyed(Vessel v) + { + if (vessel != v) return; + storage = new List(); + magicTransmitter = null; + vessel = null; + } + + public void ScheduleRebuild() + { + if (IsBusy) + { + try + { + StopCoroutine("Rebuild"); + } + catch (Exception) + { + // ignored + } + } + StartCoroutine("Rebuild"); + } + + private IEnumerator Rebuild() + { + IsBusy = true; + storage.Clear(); + List, Callback>> queuedData = magicTransmitter != null ? magicTransmitter.GetQueuedData() : new List, Callback>>(); + magicTransmitter = null; + yield return new WaitForFixedUpdate(); + if (FlightGlobals.ActiveVessel != vessel) + { + RemoveMagicTransmitter(); + } + while (FlightGlobals.ActiveVessel != null && !vessel.loaded || !FlightGlobals.ready) + { + yield return new WaitForFixedUpdate(); + } + if (FlightGlobals.ActiveVessel == null) + { + IsBusy = false; + } + else + { + vessel = FlightGlobals.ActiveVessel; + storage = vessel.FindPartModulesImplementing(); + List source = (from tx in vessel.FindPartModulesImplementing() + where !(tx is MagicDataTransmitter) + select tx).ToList(); + if (source.Any()) + { + magicTransmitter = vessel.rootPart.gameObject.GetComponent(); + if (magicTransmitter != null) + { + magicTransmitter.RefreshTransmitterQueues(queuedData); + } + else + { + // magicTransmitter = vessel.rootPart.AddModule("MagicDataTransmitter") as MagicDataTransmitter; + if (magicTransmitter != null) + magicTransmitter.cacheOwner = this; + } + } + else + { + RemoveMagicTransmitter(false); + Log.Debug("ALERT:Vessel {0} has no transmitters; no magic transmitter added", vessel.name); + } + IsBusy = false; + Log.Debug("ALERT:Rebuilt StorageCache"); + } + if (Windows.DraggableExperimentList.Instance != null) + Windows.DraggableExperimentList.Instance.CheckForCollection(); + } + + private void RemoveMagicTransmitter(bool rootOnly = true) + { + magicTransmitter = null; + if (vessel == null || vessel.rootPart == null || vessel.rootPart.Modules == null || vessel.Parts == null) return; + try + { + if (vessel.rootPart.Modules.Contains("MagicDataTransmitter")) + { + vessel.rootPart.RemoveModule(vessel.rootPart.Modules.OfType().Single()); + } + if (rootOnly) return; + for (int i = vessel.Parts.Count - 1; i >= 0; i--) + { + Part current = vessel.Parts[i]; + if (current.Modules.Contains("MagicDataTransmitter")) + { + current.RemoveModule(current.Modules.OfType().First()); + } + } + } + catch (Exception ex) + { + Log.Warning("RemoveMagicTransmitter: caught exception {0}", ex); + } + } + + public List FindStoredData(string subjectid) + { + //storage = Storage(); + List list = new List(); + foreach (IScienceDataContainer current in Storage()) //changed to look into the new list which contains all vessels + { + //if (current.GetScienceCount() <= 0) continue; //will always be true with Kerbalism installed + try + { + ScienceData[] data = current.GetData(); + for (int i = 0; i < data.Length; i++) + { + ScienceData scienceData = data[i]; + if (scienceData.subjectID == subjectid) + { + list.Add(scienceData); + } + } + } + catch (Exception ex) + { + Log.Debug("No science data", ex); + } + } + if (magicTransmitter == null) return list; + for (int i = magicTransmitter.QueuedData.Count - 1; i >=0; i--) + { + ScienceData current2 = magicTransmitter.QueuedData[i]; + + if (current2.subjectID != subjectid) continue; + list.Add(current2); + Log.Debug("ALERT:Found stored data in transmitter queue"); + } + return list; + } + + //Go through every Vessel and put their storage container into a list + public List Storage() + { + ret_storage.Clear(); + foreach (Vessel v in allVessels) + { + foreach (IScienceDataContainer container in v.FindPartModulesImplementing()) + { + ret_storage.Add(container); + } + } + return ret_storage; + } + } +} diff --git a/Source/ScienceAlert/Util.cs b/Source/ScienceAlert/Util.cs new file mode 100644 index 0000000..def96b0 --- /dev/null +++ b/Source/ScienceAlert/Util.cs @@ -0,0 +1,31 @@ +using UnityEngine; + +namespace ScienceAlert +{ + public static class Util + { + public static float CalculateNextReport(this ScienceSubject subject, ScienceExperiment experiment, System.Collections.Generic.List onboard, float xmitScalar = 1f) + { + return GetNextReportValue(subject, experiment, onboard, xmitScalar); + } + + public static float GetNextReportValue(ScienceSubject subject, ScienceExperiment experiment, System.Collections.Generic.List onboard, float xmitScalar = 1f) + { + ScienceData scienceData = new ScienceData + (experiment.baseValue * experiment.dataScale * HighLogic.CurrentGame.Parameters.Career.ScienceGainMultiplier, + xmitScalar, 0f, subject.id, string.Empty); + //scienceData.transmitBonus = ModuleScienceLab.GetBoostForVesselData(FlightGlobals.ActiveVessel, scienceData); ??? + xmitScalar += scienceData.transmitBonus; + if (onboard.Count == 0) + { + return ResearchAndDevelopment.GetScienceValue(experiment.baseValue * experiment.dataScale, subject, xmitScalar) * HighLogic.CurrentGame.Parameters.Career.ScienceGainMultiplier; + } + float num = ResearchAndDevelopment.GetNextScienceValue(experiment.baseValue * experiment.dataScale, subject, xmitScalar) * HighLogic.CurrentGame.Parameters.Career.ScienceGainMultiplier; + if (onboard.Count == 1) + { + return num; + } + return num / Mathf.Pow(4f, (float)(onboard.Count - 1)); + } + } +} diff --git a/Source/Textures/Old/btnBackground.png b/Source/Textures/Old/btnBackground.png new file mode 100644 index 0000000..57227e5 Binary files /dev/null and b/Source/Textures/Old/btnBackground.png differ diff --git a/Source/Textures/Old/btnClose.png b/Source/Textures/Old/btnClose.png new file mode 100644 index 0000000..b1a2300 Binary files /dev/null and b/Source/Textures/Old/btnClose.png differ diff --git a/Source/Textures/Old/btnDelete.png b/Source/Textures/Old/btnDelete.png new file mode 100644 index 0000000..ff5b897 Binary files /dev/null and b/Source/Textures/Old/btnDelete.png differ diff --git a/Source/Textures/Old/btnExpand.png b/Source/Textures/Old/btnExpand.png new file mode 100644 index 0000000..479aae7 Binary files /dev/null and b/Source/Textures/Old/btnExpand.png differ diff --git a/Source/Textures/Old/btnLock.png b/Source/Textures/Old/btnLock.png new file mode 100644 index 0000000..354ffdf Binary files /dev/null and b/Source/Textures/Old/btnLock.png differ diff --git a/Source/Textures/Old/btnOpen.png b/Source/Textures/Old/btnOpen.png new file mode 100644 index 0000000..13e4c83 Binary files /dev/null and b/Source/Textures/Old/btnOpen.png differ diff --git a/Source/Textures/Old/btnRename.png b/Source/Textures/Old/btnRename.png new file mode 100644 index 0000000..adb0519 Binary files /dev/null and b/Source/Textures/Old/btnRename.png differ diff --git a/Source/Textures/Old/btnSave.png b/Source/Textures/Old/btnSave.png new file mode 100644 index 0000000..e1c298b Binary files /dev/null and b/Source/Textures/Old/btnSave.png differ diff --git a/Source/Textures/Old/btnUnlock.png b/Source/Textures/Old/btnUnlock.png new file mode 100644 index 0000000..e086ce0 Binary files /dev/null and b/Source/Textures/Old/btnUnlock.png differ diff --git a/Source/Textures/Old/flask-38.png b/Source/Textures/Old/flask-38.png new file mode 100644 index 0000000..b34f39b Binary files /dev/null and b/Source/Textures/Old/flask-38.png differ diff --git a/Textures/flask.png b/Source/Textures/Old/flask.png similarity index 100% rename from Textures/flask.png rename to Source/Textures/Old/flask.png diff --git a/Source/Textures/Old/sheet-38.png b/Source/Textures/Old/sheet-38.png new file mode 100644 index 0000000..77c4f58 Binary files /dev/null and b/Source/Textures/Old/sheet-38.png differ diff --git a/Source/Textures/Old/sheet.png b/Source/Textures/Old/sheet.png new file mode 100644 index 0000000..96aa2d3 Binary files /dev/null and b/Source/Textures/Old/sheet.png differ diff --git a/Source/Textures/btnBackground.png b/Source/Textures/btnBackground.png new file mode 100644 index 0000000..57227e5 Binary files /dev/null and b/Source/Textures/btnBackground.png differ diff --git a/Source/Textures/btnClose.png b/Source/Textures/btnClose.png new file mode 100644 index 0000000..b1a2300 Binary files /dev/null and b/Source/Textures/btnClose.png differ diff --git a/Source/Textures/btnDelete.png b/Source/Textures/btnDelete.png new file mode 100644 index 0000000..ff5b897 Binary files /dev/null and b/Source/Textures/btnDelete.png differ diff --git a/Source/Textures/btnExpand.png b/Source/Textures/btnExpand.png new file mode 100644 index 0000000..479aae7 Binary files /dev/null and b/Source/Textures/btnExpand.png differ diff --git a/Source/Textures/btnLock.png b/Source/Textures/btnLock.png new file mode 100644 index 0000000..354ffdf Binary files /dev/null and b/Source/Textures/btnLock.png differ diff --git a/Source/Textures/btnOpen.png b/Source/Textures/btnOpen.png new file mode 100644 index 0000000..13e4c83 Binary files /dev/null and b/Source/Textures/btnOpen.png differ diff --git a/Source/Textures/btnRename.png b/Source/Textures/btnRename.png new file mode 100644 index 0000000..adb0519 Binary files /dev/null and b/Source/Textures/btnRename.png differ diff --git a/Source/Textures/btnSave.png b/Source/Textures/btnSave.png new file mode 100644 index 0000000..e1c298b Binary files /dev/null and b/Source/Textures/btnSave.png differ diff --git a/Source/Textures/btnUnlock.png b/Source/Textures/btnUnlock.png new file mode 100644 index 0000000..e086ce0 Binary files /dev/null and b/Source/Textures/btnUnlock.png differ diff --git a/Source/Textures/flask-38.png b/Source/Textures/flask-38.png new file mode 100644 index 0000000..5bcad7a Binary files /dev/null and b/Source/Textures/flask-38.png differ diff --git a/Source/Textures/flask.png b/Source/Textures/flask.png new file mode 100644 index 0000000..2058c08 Binary files /dev/null and b/Source/Textures/flask.png differ diff --git a/Source/Textures/flask_256.png b/Source/Textures/flask_256.png new file mode 100644 index 0000000..2d97c9f Binary files /dev/null and b/Source/Textures/flask_256.png differ diff --git a/Source/Textures/flask_64.png b/Source/Textures/flask_64.png new file mode 100644 index 0000000..d5920d1 Binary files /dev/null and b/Source/Textures/flask_64.png differ diff --git a/Source/Textures/flask_V002.zip b/Source/Textures/flask_V002.zip new file mode 100644 index 0000000..e67a375 Binary files /dev/null and b/Source/Textures/flask_V002.zip differ diff --git a/Source/Textures/flask_v001.zip b/Source/Textures/flask_v001.zip new file mode 100644 index 0000000..a84a99f Binary files /dev/null and b/Source/Textures/flask_v001.zip differ diff --git a/Source/Textures/sheet-38.png b/Source/Textures/sheet-38.png new file mode 100644 index 0000000..d272a90 Binary files /dev/null and b/Source/Textures/sheet-38.png differ diff --git a/Source/Textures/sheet.png b/Source/Textures/sheet.png new file mode 100644 index 0000000..96aa2d3 Binary files /dev/null and b/Source/Textures/sheet.png differ diff --git a/buildRelease.bat b/buildRelease.bat new file mode 100644 index 0000000..827c92e --- /dev/null +++ b/buildRelease.bat @@ -0,0 +1,74 @@ + +@echo off + +rem Put the following text into the Post-build event command line: +rem without the "rem": + +rem start /D D:\Users\jbb\github\IFI-Life-Support /WAIT deploy.bat $(TargetDir) $(TargetFileName) +rem +rem if $(ConfigurationName) == Release ( +rem +rem start /D D:\Users\jbb\github\IFI-Life-Support /WAIT buildRelease.bat $(TargetDir) $(TargetFileName) +rem +rem ) + + +rem Set variables here + +rem H is the destination game folder +rem GAMEDIR is the name of the mod folder (usually the mod name) +rem GAMEDATA is the name of the local GameData +rem VERSIONFILE is the name of the version file, usually the same as GAMEDATA, +rem but not always +rem LICENSE is the license file +rem README is the readme file + +set GAMEDIR=ScienceAlert +set GAMEDATA="GameData\" +set VERSIONFILE=%GAMEDIR%.version +set LICENSE=LICENSE.txt +set README=README.md + +set RELEASEDIR=d:\Users\jbb\release +set ZIP="c:\Program Files\7-zip\7z.exe" + +rem Copy files to GameData locations + +copy /Y "%1%2" "%GAMEDATA%\%GAMEDIR%\Plugins" +copy /Y %VERSIONFILE% %GAMEDATA%\%GAMEDIR% + +if "%LICENSE%" NEQ "" copy /y %LICENSE% %GAMEDATA%\%GAMEDIR% +if "%README%" NEQ "" copy /Y %README% %GAMEDATA%\%GAMEDIR% + +rem Get Version info + +copy %VERSIONFILE% tmp.version +set VERSIONFILE=tmp.version +rem The following requires the JQ program, available here: https://stedolan.github.io/jq/download/ +c:\local\jq-win64 ".VERSION.MAJOR" %VERSIONFILE% >tmpfile +set /P major=tmpfile +set /P minor=tmpfile +set /P patch=tmpfile +set /P build=