├── .github └── workflows │ ├── gradle.yml │ └── resource.yml ├── .gitignore ├── LICENSE.txt ├── README.md ├── build.gradle.kts ├── discord-bot ├── README.md ├── package.json └── src │ ├── bazaar │ ├── bazaar.js │ ├── bazaarConversions.json │ └── performance.js │ └── index.js ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── log4j2.xml ├── settings.gradle.kts ├── src └── main │ ├── java │ └── dev │ │ └── meyi │ │ └── bn │ │ ├── BazaarNotifier.java │ │ ├── commands │ │ └── BazaarNotifierCommand.java │ │ ├── config │ │ └── Configuration.java │ │ ├── handlers │ │ ├── ChestTickHandler.java │ │ ├── EventHandler.java │ │ ├── MouseHandler.java │ │ └── UpdateHandler.java │ │ ├── json │ │ ├── Exchange.java │ │ ├── Order.java │ │ └── resp │ │ │ ├── BazaarItem.java │ │ │ └── BazaarResponse.java │ │ ├── modules │ │ ├── Module.java │ │ ├── ModuleList.java │ │ ├── ModuleName.java │ │ ├── calc │ │ │ ├── BankCalculator.java │ │ │ ├── CraftingCalculator.java │ │ │ └── SuggestionCalculator.java │ │ └── module │ │ │ ├── BankModule.java │ │ │ ├── CraftingModule.java │ │ │ ├── NotificationModule.java │ │ │ └── SuggestionModule.java │ │ └── utilities │ │ ├── ColoredText.java │ │ ├── Defaults.java │ │ ├── ReflectionHelper.java │ │ ├── RenderUtils.java │ │ ├── ScheduledEvents.java │ │ └── Utils.java │ └── resources │ ├── icon.png │ └── mcmod.info └── wrapper ├── gradle-wrapper.jar └── gradle-wrapper.properties /.github/workflows/gradle.yml: -------------------------------------------------------------------------------- 1 | # This workflow uses actions that are not certified by GitHub. 2 | # They are provided by a third-party and are governed by 3 | # separate terms of service, privacy policy, and support 4 | # documentation. 5 | # This workflow will build a Java project with Gradle and cache/restore any dependencies to improve the workflow execution time 6 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-java-with-gradle 7 | 8 | name: Build JARs 9 | 10 | on: 11 | push: 12 | branches: [ "master" ] 13 | pull_request: 14 | branches: [ "master", "beta" ] 15 | 16 | permissions: 17 | contents: read 18 | 19 | jobs: 20 | build: 21 | 22 | runs-on: ubuntu-latest 23 | 24 | steps: 25 | - uses: actions/checkout@v3 26 | - name: Set up JDK 17 27 | uses: actions/setup-java@v3 28 | with: 29 | java-version: '17' 30 | distribution: 'temurin' 31 | - name: Build with Gradle 32 | uses: gradle/gradle-build-action@bd5760595778326ba7f1441bcf7e88b49de61a25 # v2.6.0 33 | with: 34 | arguments: build 35 | - name: Upload JARs 36 | uses: actions/upload-artifact@v4 37 | with: 38 | name: JARs 39 | path: build/libs 40 | -------------------------------------------------------------------------------- /.github/workflows/resource.yml: -------------------------------------------------------------------------------- 1 | name: 'Update Resources' 2 | 3 | on: 4 | workflow_dispatch: 5 | schedule: 6 | - cron: "0 0 * * 0" 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout repository 13 | uses: actions/checkout@v4 14 | with: 15 | ref: resources 16 | - name: Use Node.js 17 | uses: actions/setup-node@v4 18 | with: 19 | node-version: '20.x' 20 | - name: Build resources.json 21 | env: 22 | KEY: ${{ secrets.KEY }} 23 | run: | 24 | cd sync 25 | npm ci 26 | npm run build --if-present 27 | npm start 28 | - name: Create Pull Request 29 | uses: peter-evans/create-pull-request@v6 30 | with: 31 | commit-message: Automated update of resources.json 32 | title: Updating resources branch with latest version of resources.json 33 | body: This is entirely automated. If something doesn't look right, fix it! 34 | branch: update-resources 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # eclipse 2 | bin 3 | *.launch 4 | .settings 5 | .metadata 6 | .classpath 7 | .project 8 | 9 | # idea 10 | out 11 | *.ipr 12 | *.iws 13 | *.iml 14 | .idea 15 | 16 | # gradle 17 | build 18 | .gradle 19 | 20 | # other 21 | eclipse 22 | run 23 | .vscode 24 | 25 | .env 26 | node_modules 27 | .DS_STORE 28 | 29 | # resources file (downloaded from github on build) 30 | src/main/resources/resources.json 31 | 32 | sync -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Creative Commons Legal Code 2 | 3 | Attribution-NonCommercial-ShareAlike 3.0 Unported 4 | 5 | CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE 6 | LEGAL SERVICES. DISTRIBUTION OF THIS LICENSE DOES NOT CREATE AN 7 | ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS 8 | INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES 9 | REGARDING THE INFORMATION PROVIDED, AND DISCLAIMS LIABILITY FOR 10 | DAMAGES RESULTING FROM ITS USE. 11 | 12 | License 13 | 14 | THE WORK (AS DEFINED BELOW) IS PROVIDED UNDER THE TERMS OF THIS CREATIVE 15 | COMMONS PUBLIC LICENSE ("CCPL" OR "LICENSE"). THE WORK IS PROTECTED BY 16 | COPYRIGHT AND/OR OTHER APPLICABLE LAW. ANY USE OF THE WORK OTHER THAN AS 17 | AUTHORIZED UNDER THIS LICENSE OR COPYRIGHT LAW IS PROHIBITED. 18 | 19 | BY EXERCISING ANY RIGHTS TO THE WORK PROVIDED HERE, YOU ACCEPT AND AGREE 20 | TO BE BOUND BY THE TERMS OF THIS LICENSE. TO THE EXTENT THIS LICENSE MAY 21 | BE CONSIDERED TO BE A CONTRACT, THE LICENSOR GRANTS YOU THE RIGHTS 22 | CONTAINED HERE IN CONSIDERATION OF YOUR ACCEPTANCE OF SUCH TERMS AND 23 | CONDITIONS. 24 | 25 | 1. Definitions 26 | 27 | a. "Adaptation" means a work based upon the Work, or upon the Work and 28 | other pre-existing works, such as a translation, adaptation, 29 | derivative work, arrangement of music or other alterations of a 30 | literary or artistic work, or phonogram or performance and includes 31 | cinematographic adaptations or any other form in which the Work may be 32 | recast, transformed, or adapted including in any form recognizably 33 | derived from the original, except that a work that constitutes a 34 | Collection will not be considered an Adaptation for the purpose of 35 | this License. For the avoidance of doubt, where the Work is a musical 36 | work, performance or phonogram, the synchronization of the Work in 37 | timed-relation with a moving image ("synching") will be considered an 38 | Adaptation for the purpose of this License. 39 | b. "Collection" means a collection of literary or artistic works, such as 40 | encyclopedias and anthologies, or performances, phonograms or 41 | broadcasts, or other works or subject matter other than works listed 42 | in Section 1(g) below, which, by reason of the selection and 43 | arrangement of their contents, constitute intellectual creations, in 44 | which the Work is included in its entirety in unmodified form along 45 | with one or more other contributions, each constituting separate and 46 | independent works in themselves, which together are assembled into a 47 | collective whole. A work that constitutes a Collection will not be 48 | considered an Adaptation (as defined above) for the purposes of this 49 | License. 50 | c. "Distribute" means to make available to the public the original and 51 | copies of the Work or Adaptation, as appropriate, through sale or 52 | other transfer of ownership. 53 | d. "License Elements" means the following high-level license attributes 54 | as selected by Licensor and indicated in the title of this License: 55 | Attribution, Noncommercial, ShareAlike. 56 | e. "Licensor" means the individual, individuals, entity or entities that 57 | offer(s) the Work under the terms of this License. 58 | f. "Original Author" means, in the case of a literary or artistic work, 59 | the individual, individuals, entity or entities who created the Work 60 | or if no individual or entity can be identified, the publisher; and in 61 | addition (i) in the case of a performance the actors, singers, 62 | musicians, dancers, and other persons who act, sing, deliver, declaim, 63 | play in, interpret or otherwise perform literary or artistic works or 64 | expressions of folklore; (ii) in the case of a phonogram the producer 65 | being the person or legal entity who first fixes the sounds of a 66 | performance or other sounds; and, (iii) in the case of broadcasts, the 67 | organization that transmits the broadcast. 68 | g. "Work" means the literary and/or artistic work offered under the terms 69 | of this License including without limitation any production in the 70 | literary, scientific and artistic domain, whatever may be the mode or 71 | form of its expression including digital form, such as a book, 72 | pamphlet and other writing; a lecture, address, sermon or other work 73 | of the same nature; a dramatic or dramatico-musical work; a 74 | choreographic work or entertainment in dumb show; a musical 75 | composition with or without words; a cinematographic work to which are 76 | assimilated works expressed by a process analogous to cinematography; 77 | a work of drawing, painting, architecture, sculpture, engraving or 78 | lithography; a photographic work to which are assimilated works 79 | expressed by a process analogous to photography; a work of applied 80 | art; an illustration, map, plan, sketch or three-dimensional work 81 | relative to geography, topography, architecture or science; a 82 | performance; a broadcast; a phonogram; a compilation of data to the 83 | extent it is protected as a copyrightable work; or a work performed by 84 | a variety or circus performer to the extent it is not otherwise 85 | considered a literary or artistic work. 86 | h. "You" means an individual or entity exercising rights under this 87 | License who has not previously violated the terms of this License with 88 | respect to the Work, or who has received express permission from the 89 | Licensor to exercise rights under this License despite a previous 90 | violation. 91 | i. "Publicly Perform" means to perform public recitations of the Work and 92 | to communicate to the public those public recitations, by any means or 93 | process, including by wire or wireless means or public digital 94 | performances; to make available to the public Works in such a way that 95 | members of the public may access these Works from a place and at a 96 | place individually chosen by them; to perform the Work to the public 97 | by any means or process and the communication to the public of the 98 | performances of the Work, including by public digital performance; to 99 | broadcast and rebroadcast the Work by any means including signs, 100 | sounds or images. 101 | j. "Reproduce" means to make copies of the Work by any means including 102 | without limitation by sound or visual recordings and the right of 103 | fixation and reproducing fixations of the Work, including storage of a 104 | protected performance or phonogram in digital form or other electronic 105 | medium. 106 | 107 | 2. Fair Dealing Rights. Nothing in this License is intended to reduce, 108 | limit, or restrict any uses free from copyright or rights arising from 109 | limitations or exceptions that are provided for in connection with the 110 | copyright protection under copyright law or other applicable laws. 111 | 112 | 3. License Grant. Subject to the terms and conditions of this License, 113 | Licensor hereby grants You a worldwide, royalty-free, non-exclusive, 114 | perpetual (for the duration of the applicable copyright) license to 115 | exercise the rights in the Work as stated below: 116 | 117 | a. to Reproduce the Work, to incorporate the Work into one or more 118 | Collections, and to Reproduce the Work as incorporated in the 119 | Collections; 120 | b. to create and Reproduce Adaptations provided that any such Adaptation, 121 | including any translation in any medium, takes reasonable steps to 122 | clearly label, demarcate or otherwise identify that changes were made 123 | to the original Work. For example, a translation could be marked "The 124 | original work was translated from English to Spanish," or a 125 | modification could indicate "The original work has been modified."; 126 | c. to Distribute and Publicly Perform the Work including as incorporated 127 | in Collections; and, 128 | d. to Distribute and Publicly Perform Adaptations. 129 | 130 | The above rights may be exercised in all media and formats whether now 131 | known or hereafter devised. The above rights include the right to make 132 | such modifications as are technically necessary to exercise the rights in 133 | other media and formats. Subject to Section 8(f), all rights not expressly 134 | granted by Licensor are hereby reserved, including but not limited to the 135 | rights described in Section 4(e). 136 | 137 | 4. Restrictions. The license granted in Section 3 above is expressly made 138 | subject to and limited by the following restrictions: 139 | 140 | a. You may Distribute or Publicly Perform the Work only under the terms 141 | of this License. You must include a copy of, or the Uniform Resource 142 | Identifier (URI) for, this License with every copy of the Work You 143 | Distribute or Publicly Perform. You may not offer or impose any terms 144 | on the Work that restrict the terms of this License or the ability of 145 | the recipient of the Work to exercise the rights granted to that 146 | recipient under the terms of the License. You may not sublicense the 147 | Work. You must keep intact all notices that refer to this License and 148 | to the disclaimer of warranties with every copy of the Work You 149 | Distribute or Publicly Perform. When You Distribute or Publicly 150 | Perform the Work, You may not impose any effective technological 151 | measures on the Work that restrict the ability of a recipient of the 152 | Work from You to exercise the rights granted to that recipient under 153 | the terms of the License. This Section 4(a) applies to the Work as 154 | incorporated in a Collection, but this does not require the Collection 155 | apart from the Work itself to be made subject to the terms of this 156 | License. If You create a Collection, upon notice from any Licensor You 157 | must, to the extent practicable, remove from the Collection any credit 158 | as required by Section 4(d), as requested. If You create an 159 | Adaptation, upon notice from any Licensor You must, to the extent 160 | practicable, remove from the Adaptation any credit as required by 161 | Section 4(d), as requested. 162 | b. You may Distribute or Publicly Perform an Adaptation only under: (i) 163 | the terms of this License; (ii) a later version of this License with 164 | the same License Elements as this License; (iii) a Creative Commons 165 | jurisdiction license (either this or a later license version) that 166 | contains the same License Elements as this License (e.g., 167 | Attribution-NonCommercial-ShareAlike 3.0 US) ("Applicable License"). 168 | You must include a copy of, or the URI, for Applicable License with 169 | every copy of each Adaptation You Distribute or Publicly Perform. You 170 | may not offer or impose any terms on the Adaptation that restrict the 171 | terms of the Applicable License or the ability of the recipient of the 172 | Adaptation to exercise the rights granted to that recipient under the 173 | terms of the Applicable License. You must keep intact all notices that 174 | refer to the Applicable License and to the disclaimer of warranties 175 | with every copy of the Work as included in the Adaptation You 176 | Distribute or Publicly Perform. When You Distribute or Publicly 177 | Perform the Adaptation, You may not impose any effective technological 178 | measures on the Adaptation that restrict the ability of a recipient of 179 | the Adaptation from You to exercise the rights granted to that 180 | recipient under the terms of the Applicable License. This Section 4(b) 181 | applies to the Adaptation as incorporated in a Collection, but this 182 | does not require the Collection apart from the Adaptation itself to be 183 | made subject to the terms of the Applicable License. 184 | c. You may not exercise any of the rights granted to You in Section 3 185 | above in any manner that is primarily intended for or directed toward 186 | commercial advantage or private monetary compensation. The exchange of 187 | the Work for other copyrighted works by means of digital file-sharing 188 | or otherwise shall not be considered to be intended for or directed 189 | toward commercial advantage or private monetary compensation, provided 190 | there is no payment of any monetary compensation in con-nection with 191 | the exchange of copyrighted works. 192 | d. If You Distribute, or Publicly Perform the Work or any Adaptations or 193 | Collections, You must, unless a request has been made pursuant to 194 | Section 4(a), keep intact all copyright notices for the Work and 195 | provide, reasonable to the medium or means You are utilizing: (i) the 196 | name of the Original Author (or pseudonym, if applicable) if supplied, 197 | and/or if the Original Author and/or Licensor designate another party 198 | or parties (e.g., a sponsor institute, publishing entity, journal) for 199 | attribution ("Attribution Parties") in Licensor's copyright notice, 200 | terms of service or by other reasonable means, the name of such party 201 | or parties; (ii) the title of the Work if supplied; (iii) to the 202 | extent reasonably practicable, the URI, if any, that Licensor 203 | specifies to be associated with the Work, unless such URI does not 204 | refer to the copyright notice or licensing information for the Work; 205 | and, (iv) consistent with Section 3(b), in the case of an Adaptation, 206 | a credit identifying the use of the Work in the Adaptation (e.g., 207 | "French translation of the Work by Original Author," or "Screenplay 208 | based on original Work by Original Author"). The credit required by 209 | this Section 4(d) may be implemented in any reasonable manner; 210 | provided, however, that in the case of a Adaptation or Collection, at 211 | a minimum such credit will appear, if a credit for all contributing 212 | authors of the Adaptation or Collection appears, then as part of these 213 | credits and in a manner at least as prominent as the credits for the 214 | other contributing authors. For the avoidance of doubt, You may only 215 | use the credit required by this Section for the purpose of attribution 216 | in the manner set out above and, by exercising Your rights under this 217 | License, You may not implicitly or explicitly assert or imply any 218 | connection with, sponsorship or endorsement by the Original Author, 219 | Licensor and/or Attribution Parties, as appropriate, of You or Your 220 | use of the Work, without the separate, express prior written 221 | permission of the Original Author, Licensor and/or Attribution 222 | Parties. 223 | e. For the avoidance of doubt: 224 | 225 | i. Non-waivable Compulsory License Schemes. In those jurisdictions in 226 | which the right to collect royalties through any statutory or 227 | compulsory licensing scheme cannot be waived, the Licensor 228 | reserves the exclusive right to collect such royalties for any 229 | exercise by You of the rights granted under this License; 230 | ii. Waivable Compulsory License Schemes. In those jurisdictions in 231 | which the right to collect royalties through any statutory or 232 | compulsory licensing scheme can be waived, the Licensor reserves 233 | the exclusive right to collect such royalties for any exercise by 234 | You of the rights granted under this License if Your exercise of 235 | such rights is for a purpose or use which is otherwise than 236 | noncommercial as permitted under Section 4(c) and otherwise waives 237 | the right to collect royalties through any statutory or compulsory 238 | licensing scheme; and, 239 | iii. Voluntary License Schemes. The Licensor reserves the right to 240 | collect royalties, whether individually or, in the event that the 241 | Licensor is a member of a collecting society that administers 242 | voluntary licensing schemes, via that society, from any exercise 243 | by You of the rights granted under this License that is for a 244 | purpose or use which is otherwise than noncommercial as permitted 245 | under Section 4(c). 246 | f. Except as otherwise agreed in writing by the Licensor or as may be 247 | otherwise permitted by applicable law, if You Reproduce, Distribute or 248 | Publicly Perform the Work either by itself or as part of any 249 | Adaptations or Collections, You must not distort, mutilate, modify or 250 | take other derogatory action in relation to the Work which would be 251 | prejudicial to the Original Author's honor or reputation. Licensor 252 | agrees that in those jurisdictions (e.g. Japan), in which any exercise 253 | of the right granted in Section 3(b) of this License (the right to 254 | make Adaptations) would be deemed to be a distortion, mutilation, 255 | modification or other derogatory action prejudicial to the Original 256 | Author's honor and reputation, the Licensor will waive or not assert, 257 | as appropriate, this Section, to the fullest extent permitted by the 258 | applicable national law, to enable You to reasonably exercise Your 259 | right under Section 3(b) of this License (right to make Adaptations) 260 | but not otherwise. 261 | 262 | 5. Representations, Warranties and Disclaimer 263 | 264 | UNLESS OTHERWISE MUTUALLY AGREED TO BY THE PARTIES IN WRITING AND TO THE 265 | FULLEST EXTENT PERMITTED BY APPLICABLE LAW, LICENSOR OFFERS THE WORK AS-IS 266 | AND MAKES NO REPRESENTATIONS OR WARRANTIES OF ANY KIND CONCERNING THE 267 | WORK, EXPRESS, IMPLIED, STATUTORY OR OTHERWISE, INCLUDING, WITHOUT 268 | LIMITATION, WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR 269 | PURPOSE, NONINFRINGEMENT, OR THE ABSENCE OF LATENT OR OTHER DEFECTS, 270 | ACCURACY, OR THE PRESENCE OF ABSENCE OF ERRORS, WHETHER OR NOT 271 | DISCOVERABLE. SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION OF IMPLIED 272 | WARRANTIES, SO THIS EXCLUSION MAY NOT APPLY TO YOU. 273 | 274 | 6. Limitation on Liability. EXCEPT TO THE EXTENT REQUIRED BY APPLICABLE 275 | LAW, IN NO EVENT WILL LICENSOR BE LIABLE TO YOU ON ANY LEGAL THEORY FOR 276 | ANY SPECIAL, INCIDENTAL, CONSEQUENTIAL, PUNITIVE OR EXEMPLARY DAMAGES 277 | ARISING OUT OF THIS LICENSE OR THE USE OF THE WORK, EVEN IF LICENSOR HAS 278 | BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 279 | 280 | 7. Termination 281 | 282 | a. This License and the rights granted hereunder will terminate 283 | automatically upon any breach by You of the terms of this License. 284 | Individuals or entities who have received Adaptations or Collections 285 | from You under this License, however, will not have their licenses 286 | terminated provided such individuals or entities remain in full 287 | compliance with those licenses. Sections 1, 2, 5, 6, 7, and 8 will 288 | survive any termination of this License. 289 | b. Subject to the above terms and conditions, the license granted here is 290 | perpetual (for the duration of the applicable copyright in the Work). 291 | Notwithstanding the above, Licensor reserves the right to release the 292 | Work under different license terms or to stop distributing the Work at 293 | any time; provided, however that any such election will not serve to 294 | withdraw this License (or any other license that has been, or is 295 | required to be, granted under the terms of this License), and this 296 | License will continue in full force and effect unless terminated as 297 | stated above. 298 | 299 | 8. Miscellaneous 300 | 301 | a. Each time You Distribute or Publicly Perform the Work or a Collection, 302 | the Licensor offers to the recipient a license to the Work on the same 303 | terms and conditions as the license granted to You under this License. 304 | b. Each time You Distribute or Publicly Perform an Adaptation, Licensor 305 | offers to the recipient a license to the original Work on the same 306 | terms and conditions as the license granted to You under this License. 307 | c. If any provision of this License is invalid or unenforceable under 308 | applicable law, it shall not affect the validity or enforceability of 309 | the remainder of the terms of this License, and without further action 310 | by the parties to this agreement, such provision shall be reformed to 311 | the minimum extent necessary to make such provision valid and 312 | enforceable. 313 | d. No term or provision of this License shall be deemed waived and no 314 | breach consented to unless such waiver or consent shall be in writing 315 | and signed by the party to be charged with such waiver or consent. 316 | e. This License constitutes the entire agreement between the parties with 317 | respect to the Work licensed here. There are no understandings, 318 | agreements or representations with respect to the Work not specified 319 | here. Licensor shall not be bound by any additional provisions that 320 | may appear in any communication from You. This License may not be 321 | modified without the mutual written agreement of the Licensor and You. 322 | f. The rights granted under, and the subject matter referenced, in this 323 | License were drafted utilizing the terminology of the Berne Convention 324 | for the Protection of Literary and Artistic Works (as amended on 325 | September 28, 1979), the Rome Convention of 1961, the WIPO Copyright 326 | Treaty of 1996, the WIPO Performances and Phonograms Treaty of 1996 327 | and the Universal Copyright Convention (as revised on July 24, 1971). 328 | These rights and subject matter take effect in the relevant 329 | jurisdiction in which the License terms are sought to be enforced 330 | according to the corresponding provisions of the implementation of 331 | those treaty provisions in the applicable national law. If the 332 | standard suite of rights granted under applicable copyright law 333 | includes additional rights not granted under this License, such 334 | additional rights are deemed to be included in the License; this 335 | License is not intended to restrict the license of any rights under 336 | applicable law. 337 | 338 | 339 | Creative Commons Notice 340 | 341 | Creative Commons is not a party to this License, and makes no warranty 342 | whatsoever in connection with the Work. Creative Commons will not be 343 | liable to You or any party on any legal theory for any damages 344 | whatsoever, including without limitation any general, special, 345 | incidental or consequential damages arising in connection to this 346 | license. Notwithstanding the foregoing two (2) sentences, if Creative 347 | Commons has expressly identified itself as the Licensor hereunder, it 348 | shall have all rights and obligations of Licensor. 349 | 350 | Except for the limited purpose of indicating to the public that the 351 | Work is licensed under the CCPL, Creative Commons does not authorize 352 | the use by either party of the trademark "Creative Commons" or any 353 | related trademark or logo of Creative Commons without the prior 354 | written consent of Creative Commons. Any permitted use will be in 355 | compliance with Creative Commons' then-current trademark usage 356 | guidelines, as may be published on its website or otherwise made 357 | available upon request from time to time. For the avoidance of doubt, 358 | this trademark restriction does not form part of this License. 359 | 360 | Creative Commons may be contacted at https://creativecommons.org/. 361 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # BazaarNotifier 2 | 3 | Mod and Discord Bot for Hypixel Skyblock that helps with tracking bazaar sell offer and buy order movements 4 | 5 | 6 | 7 | All bug reports should be in [issues](https://github.com/symt/BazaarNotifier/issues) or in the discord server. 8 | 9 | 10 | ## Building 11 | 12 | This project is now built on [this template](https://github.com/romangraef/Forge1.8.9Template/). Build instructions from there: 13 | 14 | > To run the mod you will need two JDKs, one Java 17 jdk and one Java 1.8 jdk. 15 | 16 | > When you import your project into IntelliJ, you need to set the gradle jvm to the Java 17 JDK in the gradle tab, and the Project SDK to the Java 1.8 JDK. Then click on the sync button in IntelliJ, and it should create a run task called Minecraft Client. If it doesn't then try relaunching your IntelliJ. Warning for Mac users: You might have to remove the -XStartOnFirstThread vm argument from your run configuration. 17 | 18 | You can also build in the command line. Set your JAVA_HOME environment variable to the path to the 1.8 JDK and then run 19 | 20 | ``` 21 | ./gradlew build -Dorg.gradle.java.home=/PATH/TO/JDK17 22 | ``` 23 | 24 | ## Extras 25 | 26 | Join the discord server: https://discord.com/invite/wjpJSVSwvD 27 | 28 | 29 |
30 | 31 | [License](https://github.com/symt/BazaarNotifier/blob/master/license.txt) | [Site](https://meyi.dev) 32 | -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | import de.undercouch.gradle.tasks.download.Download 2 | import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar 3 | 4 | plugins { 5 | idea 6 | java 7 | id("gg.essential.loom") version "0.10.0.+" 8 | id("dev.architectury.architectury-pack200") version "0.1.3" 9 | id("de.undercouch.download").version("5.3.0") 10 | id("com.github.johnrengelman.shadow") version "7.1.2" 11 | 12 | } 13 | 14 | group = "dev.meyi.bazaarnotifier" 15 | version = "1.7.5" 16 | val mod_id = "bazaarnotifier" 17 | 18 | java { 19 | toolchain.languageVersion.set(JavaLanguageVersion.of(8)) 20 | } 21 | 22 | loom { 23 | launchConfigs.named("client") { 24 | arg("--tweakClass", "cc.polyfrost.oneconfig.loader.stage0.LaunchWrapperTweaker") 25 | } 26 | log4jConfigs.from(file("log4j2.xml")) 27 | forge { 28 | pack200Provider.set(dev.architectury.pack200.java.Pack200Adapter()) 29 | } 30 | } 31 | 32 | sourceSets.main { 33 | output.setResourcesDir(file("$buildDir/classes/java/main")) 34 | } 35 | 36 | val shade: Configuration by configurations.creating { 37 | configurations.implementation.get().extendsFrom(this) 38 | } 39 | 40 | repositories { 41 | mavenCentral() 42 | maven("https://repo.spongepowered.org/maven/") 43 | maven("https://repo.polyfrost.cc/releases") 44 | } 45 | 46 | dependencies { 47 | minecraft("com.mojang:minecraft:1.8.9") 48 | mappings("de.oceanlabs.mcp:mcp_stable:22-1.8.9") 49 | forge("net.minecraftforge:forge:1.8.9-11.15.1.2318-1.8.9") 50 | modCompileOnly("cc.polyfrost:oneconfig-1.8.9-forge:0.2.2-alpha+") 51 | shade("cc.polyfrost:oneconfig-wrapper-launchwrapper:1.0.0-beta17") 52 | } 53 | 54 | 55 | 56 | val resourcesFile = "src/main/resources/resources.json" 57 | val resourcesURL = "https://raw.githubusercontent.com/symt/BazaarNotifier/resources/resources.json" 58 | 59 | tasks.processResources { 60 | duplicatesStrategy = DuplicatesStrategy.INCLUDE 61 | 62 | inputs.property("version", project.version) 63 | inputs.property("mcversion", "1.8.9") 64 | from(sourceSets["main"].resources.srcDirs) { 65 | include("mcmod.info") 66 | expand("version" to project.version, "mcversion" to "1.8.9") 67 | } 68 | from(sourceSets["main"].resources.srcDirs) { 69 | exclude("mcmod.info") 70 | } 71 | 72 | dependsOn("retrieveResources") 73 | finalizedBy("destroyResources") 74 | outputs.upToDateWhen { false } 75 | } 76 | 77 | task("destroyResources") { 78 | doLast { 79 | if (File(resourcesFile).exists()) { 80 | project.delete(files(resourcesFile)) 81 | } 82 | } 83 | outputs.upToDateWhen { false } 84 | } 85 | 86 | task("retrieveResources") { 87 | val dest = File(resourcesFile) 88 | 89 | if (dest.exists()) { 90 | project.delete(files(resourcesFile)) 91 | } 92 | task("download-task") { 93 | src(resourcesURL) 94 | dest(resourcesFile) 95 | } 96 | dependsOn("download-task") 97 | } 98 | 99 | tasks{ 100 | withType(JavaCompile::class) { 101 | options.encoding = "UTF-8" 102 | } 103 | withType(Jar::class) { 104 | manifest.attributes.run { 105 | this["FMLCorePluginContainsFMLMod"] = "true" 106 | this["ForceLoadAsMod"] = "true" 107 | } 108 | named("shadowJar") { 109 | archiveClassifier.set("dev") 110 | configurations = listOf(shade) 111 | duplicatesStrategy = DuplicatesStrategy.EXCLUDE 112 | } 113 | 114 | remapJar { 115 | input.set(shadowJar.get().archiveFile) 116 | archiveClassifier.set("") 117 | } 118 | 119 | jar { 120 | manifest { 121 | attributes( 122 | mapOf( 123 | "ModSide" to "CLIENT", 124 | "ForceLoadAsMod" to true, 125 | "TweakOrder" to "0", 126 | "TweakClass" to "cc.polyfrost.oneconfig.loader.stage0.LaunchWrapperTweaker" 127 | ) 128 | ) 129 | } 130 | dependsOn(shadowJar) 131 | archiveClassifier.set("") 132 | enabled = false 133 | } 134 | processResources { 135 | duplicatesStrategy = DuplicatesStrategy.INCLUDE 136 | 137 | } 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /discord-bot/README.md: -------------------------------------------------------------------------------- 1 | ## Bazaar Bot 2 | 3 | 4 | This is the bot that's used at the discord server: https://discord.gg/sjNFags 5 | 6 | Currently, it should work in every server. The `~find` command, however, hasn't been implemented to work for every server yet. You can implement it for other servers by changing the channel id, but it won't work if the instance isn't yours. 7 | 8 | To setup, you need a .env file for storing the hypixel api key and the bot's token: 9 | ``` 10 | HYPIXEL_API_KEY=___________________________ 11 | BOT=_______________________ 12 | CHANNEL=______________ 13 | ``` 14 | 15 | To set the channel in which the bot spits out the information, go into index.js and change the channel variable to the id of the channel of your choosing. You can also modify the .env file as seen above. The prefix, defaulting to `~`, can be set in index.js. 16 | 17 | If you want to merge the bot with your own, change the prefix, client and channel in the import variables of bazaar/bazaar.js to your own and run cacheUpdate function in your ready event (it will automatically run the rest of the files). 18 | 19 | -------------------------------------------------------------------------------- /discord-bot/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bazaar-bot", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "./src/index.js", 6 | "scripts": { 7 | "start": "node ./src/index.js" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "axios": "^0.27.2", 14 | "discord.js": "^13.7.0", 15 | "dotenv": "^8.2.0" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /discord-bot/src/bazaar/bazaar.js: -------------------------------------------------------------------------------- 1 | const {MessageEmbed} = require('discord.js'); 2 | const {client, prefix, channel} = require('../index'); 3 | const axios = require("axios").default; 4 | const bazaarConversions = require("./bazaarConversions.json"); 5 | const performance = require("./performance.js"); 6 | 7 | const toTitleCase = (phrase) => { 8 | return phrase 9 | .toLowerCase() 10 | .split(" ") 11 | .map((word) => word.charAt(0).toUpperCase() + word.slice(1)) 12 | .join(" "); 13 | }; 14 | 15 | let inRequest = false; 16 | let bazaarCache = {}; 17 | 18 | client.on("messageCreate", (message) => { 19 | if ( 20 | !message.content.startsWith(prefix) || 21 | !message.channel.id === "733213711157821452" 22 | ) { 23 | return; 24 | } 25 | 26 | if (message.content.startsWith(prefix + "find") && message.content.split( 27 | " ").length >= 2) { 28 | 29 | let item = message.content.slice(6).toLowerCase().trim(); 30 | if (bazaarCache[item]) { 31 | let data = bazaarCache[item]; 32 | message.channel.send({ 33 | embeds: [ 34 | new MessageEmbed() 35 | .setTitle(toTitleCase(item)) 36 | .setColor(Math.floor(Math.random() * 16777215)) 37 | .setDescription( 38 | `**Buy Order**: ${data.buyOrderPrice}\n**Sell Order**: ${ 39 | Math.round(data.sellOrderPrice * 99) / 100 40 | }\n**Difference**: ${ 41 | Math.round( 42 | (data.sellOrderPrice * 0.99 - data.buyOrderPrice) * 100 43 | ) / 100 44 | }\n**Estimated Profit (coins/minute)**: ${ 45 | Math.round(data.profitFlowPerMinute * 100) / 100 46 | }` 47 | ) 48 | ] 49 | }); 50 | } else { 51 | message.reply({ 52 | content: `The item searched (**${item}**) doesn't exist. Maybe something went wrong on my end, but odds are you just messed up!` 53 | }); 54 | } 55 | } 56 | }); 57 | 58 | const cacheUpdate = async () => { 59 | try { 60 | if (client && !inRequest) { 61 | inRequest = true; 62 | let bazaarData = []; 63 | let key = process.env.HYPIXEL_API_KEY; 64 | await axios({ 65 | method: "GET", 66 | url: `https://api.hypixel.net/skyblock/bazaar?key=${key}`, 67 | }) 68 | .then((res) => { 69 | let products = res.data.products; 70 | let productIds = Object.keys(res.data.products); 71 | 72 | productIds.forEach((id) => { 73 | let product = products[id]; 74 | bazaarData.push({ 75 | productId: bazaarConversions[id] || id, 76 | sellOrderPrice: 77 | product.buy_summary[0] && product.sell_summary[0] 78 | ? product.buy_summary[0].pricePerUnit 79 | : 0, 80 | buyOrderPrice: 81 | product.sell_summary[0] && product.buy_summary[0] 82 | ? product.sell_summary[0].pricePerUnit 83 | : 0, 84 | sellCount: product.quick_status.buyMovingWeek, 85 | buyCount: product.quick_status.sellMovingWeek, 86 | }); 87 | }); 88 | }) 89 | .catch((e) => { 90 | console.log(e); 91 | }); 92 | 93 | bazaarData = performance(bazaarData); 94 | 95 | bazaarData.forEach((data) => { 96 | bazaarCache[data.productId.toLowerCase()] = data; 97 | }); 98 | 99 | bazaarData = bazaarData.slice(0, 16); 100 | 101 | let fields = []; 102 | let i = 1; 103 | bazaarData.forEach((data) => { 104 | fields.push({ 105 | name: `${i++}. ${data.productId}`, 106 | value: `EP: ${Math.round(data.profitFlowPerMinute * 100) / 100}`, 107 | inline: true, 108 | }); 109 | if (i % 2 == 0) { 110 | fields.push({name: "\u200B", value: "\u200B", inline: true}); 111 | } 112 | }); 113 | let embed = new MessageEmbed() 114 | .setTitle("Bazaar") 115 | .setColor(Math.floor(Math.random() * 16777215)) 116 | .addFields(...fields) 117 | .setFooter({ 118 | text: "EP is about how much money you'd make while flipping an item per minute of flipping. It assumes you miss no instants." 119 | }); 120 | client.channels.cache.get(channel).send({embeds: [embed]}); 121 | inRequest = false; 122 | } 123 | } catch (e) { 124 | inRequest = false; 125 | console.log(e); 126 | } 127 | } 128 | 129 | setInterval(cacheUpdate, 30000); 130 | 131 | module.exports = {cacheUpdate}; 132 | -------------------------------------------------------------------------------- /discord-bot/src/bazaar/bazaarConversions.json: -------------------------------------------------------------------------------- 1 | { 2 | "ENCHANTED_RAW_CHICKEN": "Enchanted Raw Chicken", 3 | "INK_SACK:3": "Cocoa Beans", 4 | "BROWN_MUSHROOM": "Brown Mushroom", 5 | "ENCHANTED_WATER_LILY": "Enchanted Lily Pad", 6 | "INK_SACK:4": "Lapis Lazuli", 7 | "TARANTULA_WEB": "Tarantula Web", 8 | "CARROT_ITEM": "Carrot", 9 | "ENCHANTED_POTATO": "Enchanted Potato", 10 | "LOG:1": "Spruce Log", 11 | "ENCHANTED_SLIME_BALL": "Enchanted Slime Ball", 12 | "ENCHANTED_GOLDEN_CARROT": "Enchanted Golden Carrot", 13 | "LOG:3": "Jungle Log", 14 | "LOG:2": "Birch Log", 15 | "ENCHANTED_RABBIT_HIDE": "Enchanted Rabbit Hide", 16 | "ENCHANTED_GLOWSTONE_DUST": "Enchanted Glowstone Dust", 17 | "ENCHANTED_INK_SACK": "Enchanted Ink Sack", 18 | "ENCHANTED_CACTUS": "Enchanted Cactus", 19 | "ENCHANTED_SUGAR_CANE": "Enchanted Sugar Cane", 20 | "ENCHANTED_BIRCH_LOG": "Enchanted Birch Log", 21 | "ENCHANTED_GUNPOWDER": "Enchanted Gunpowder", 22 | "ENCHANTED_MELON": "Enchanted Melon", 23 | "ENCHANTED_COOKED_SALMON": "Enchanted Cooked Salmon", 24 | "ENCHANTED_SUGAR": "Enchanted Sugar", 25 | "LOG": "Oak Log", 26 | "CACTUS": "Cactus", 27 | "ENCHANTED_BLAZE_ROD": "Enchanted Blaze Rod", 28 | "GHAST_TEAR": "Ghast Tear", 29 | "ENCHANTED_CAKE": "Enchanted Cake", 30 | "PUMPKIN": "Pumpkin", 31 | "ENCHANTED_ENDER_PEARL": "Enchanted Ender Pearl", 32 | "PURPLE_CANDY": "Purple Candy", 33 | "WHEAT": "Wheat", 34 | "ENCHANTED_FERMENTED_SPIDER_EYE": "Enchanted Fermented Spider Eye", 35 | "ENCHANTED_GOLD_BLOCK": "Enchanted Gold Block", 36 | "ENCHANTED_RAW_SALMON": "Enchanted Raw Salmon", 37 | "ENCHANTED_JUNGLE_LOG": "Enchanted Jungle Log", 38 | "ENCHANTED_FLINT": "Enchanted Flint", 39 | "ENCHANTED_GLISTERING_MELON": "Enchanted Glistering Melon", 40 | "IRON_INGOT": "Iron Ingot", 41 | "PRISMARINE_SHARD": "Prismarine Shard", 42 | "ENCHANTED_EMERALD": "Enchanted Emerald", 43 | "ENCHANTED_SPIDER_EYE": "Enchanted Spider Eye", 44 | "ENCHANTED_EMERALD_BLOCK": "Enchanted Emerald Block", 45 | "RED_MUSHROOM": "Red Mushroom", 46 | "MUTTON": "Mutton", 47 | "ENCHANTED_MELON_BLOCK": "Enchanted Melon Block", 48 | "ENCHANTED_CLAY_BALL": "Enchanted Clay Ball", 49 | "DIAMOND": "Diamond", 50 | "COBBLESTONE": "Cobblestone", 51 | "SPIDER_EYE": "Spider Eye", 52 | "RAW_FISH": "Raw Fish", 53 | "ENCHANTED_PUFFERFISH": "Enchanted Pufferfish", 54 | "GLOWSTONE_DUST": "Glowstone Dust", 55 | "GOLD_INGOT": "Gold Ingot", 56 | "REVENANT_VISCERA": "Revenant Viscera", 57 | "TARANTULA_SILK": "Tarantula Silk", 58 | "POTATO_ITEM": "Potato", 59 | "ENCHANTED_MUTTON": "Enchanted Mutton", 60 | "ENCHANTED_HUGE_MUSHROOM_1": "Enchanted Brown Mushroom Block", 61 | "SUPER_COMPACTOR_3000": "Super Compactor 3000", 62 | "ENCHANTED_IRON": "Enchanted Iron", 63 | "SUPER_EGG": "Super Egg", 64 | "STOCK_OF_STONKS": "Stock Of Stonks", 65 | "ENCHANTED_COBBLESTONE": "Enchanted Cobblestone", 66 | "ENCHANTED_BONE": "Enchanted Bone", 67 | "ENCHANTED_PAPER": "Enchanted Paper", 68 | "ENCHANTED_HUGE_MUSHROOM_2": "Enchanted Red Mushroom Block", 69 | "PORK": "Raw Porkchop", 70 | "ENCHANTED_DIAMOND_BLOCK": "Enchanted Diamond Block", 71 | "EMERALD": "Emerald", 72 | "ENCHANTED_RABBIT_FOOT": "Enchanted Rabbit Foot", 73 | "PRISMARINE_CRYSTALS": "Prismarine Crystals", 74 | "HOT_POTATO_BOOK": "Hot Potato Book", 75 | "ENCHANTED_ICE": "Enchanted Ice", 76 | "ICE": "Ice", 77 | "CLAY_BALL": "Clay Ball", 78 | "HUGE_MUSHROOM_1": "Brown Mushroom Block", 79 | "HUGE_MUSHROOM_2": "Red Mushroom Block", 80 | "LOG_2:1": "Dark Oak Log", 81 | "GREEN_GIFT": "Green Gift", 82 | "ENCHANTED_SNOW_BLOCK": "Enchanted Snow Block", 83 | "GOLDEN_TOOTH": "Golden Tooth", 84 | "STRING": "String", 85 | "PACKED_ICE": "Packed Ice", 86 | "WATER_LILY": "Lily Pad", 87 | "RABBIT_FOOT": "Rabbit's Foot", 88 | "LOG_2": "Acacia Log", 89 | "REDSTONE": "Redstone", 90 | "ENCHANTED_OBSIDIAN": "Enchanted Obsidian", 91 | "ENCHANTED_COAL": "Enchanted Coal", 92 | "COAL": "Coal", 93 | "ENCHANTED_QUARTZ": "Enchanted Quartz", 94 | "ENDER_PEARL": "Ender Pearl", 95 | "ENCHANTED_COAL_BLOCK": "Enchanted Coal Block", 96 | "ENCHANTED_CACTUS_GREEN": "Enchanted Cactus Green", 97 | "ENCHANTED_PRISMARINE_CRYSTALS": "Enchanted Prismarine Crystals", 98 | "ENCHANTED_CARROT_STICK": "Enchanted Carrot On A Stick", 99 | "ENCHANTED_CARROT_ON_A_STICK": "Enchanted Carrot On A Stick", 100 | "ENCHANTED_ENDSTONE": "Enchanted End Stone", 101 | "ENCHANTED_LAPIS_LAZULI_BLOCK": "Enchanted Lapis Lazuli Block", 102 | "ENCHANTED_COOKIE": "Enchanted Cookie", 103 | "ENCHANTED_STRING": "Enchanted String", 104 | "SLIME_BALL": "Slime Ball", 105 | "ENDER_STONE": "End Stone", 106 | "ENCHANTED_RAW_FISH": "Enchanted Raw Fish", 107 | "ENCHANTED_ACACIA_LOG": "Enchanted Acacia Log", 108 | "SNOW_BALL": "Snow Ball", 109 | "ENCHANTED_EGG": "Enchanted Egg", 110 | "QUARTZ": "Nether Quartz", 111 | "RAW_BEEF": "Raw Beef", 112 | "ENCHANTED_EYE_OF_ENDER": "Enchanted Eye of Ender", 113 | "SAND": "Sand", 114 | "RAW_CHICKEN": "Raw Chicken", 115 | "MAGMA_CREAM": "Magma Cream", 116 | "SUGAR_CANE": "Sugar Cane", 117 | "ENCHANTED_LAPIS_LAZULI": "Enchanted Lapis Lazuli", 118 | "ENCHANTED_GHAST_TEAR": "Enchanted Ghast Tear", 119 | "ENCHANTED_COCOA": "Enchanted Cocoa", 120 | "RED_GIFT": "Red Gift", 121 | "ENCHANTED_RAW_BEEF": "Enchanted Raw Beef", 122 | "SEEDS": "Seeds", 123 | "ENCHANTED_LEATHER": "Enchanted Leather", 124 | "ENCHANTED_SPONGE": "Enchanted Sponge", 125 | "ENCHANTED_FEATHER": "Enchanted Feather", 126 | "ENCHANTED_SLIME_BLOCK": "Enchanted Slime Block", 127 | "ENCHANTED_OAK_LOG": "Enchanted Oak Log", 128 | "RABBIT_HIDE": "Rabbit Hide", 129 | "WHITE_GIFT": "White Gift", 130 | "INK_SACK": "Ink Sack", 131 | "FLINT": "Flint", 132 | "ENCHANTED_SPRUCE_LOG": "Enchanted Spruce Log", 133 | "WOLF_TOOTH": "Wolf Tooth", 134 | "ENCHANTED_ROTTEN_FLESH": "Enchanted Rotten Flesh", 135 | "ENCHANTED_GRILLED_PORK": "Enchanted Grilled Pork", 136 | "SULPHUR": "Gunpowder", 137 | "NETHER_STALK": "Nether Wart", 138 | "RABBIT": "Raw Rabbit", 139 | "ENCHANTED_NETHER_STALK": "Enchanted Nether Wart", 140 | "ENCHANTED_REDSTONE_BLOCK": "Enchanted Redstone Block", 141 | "ENCHANTED_QUARTZ_BLOCK": "Enchanted Quartz Block", 142 | "ENCHANTED_CARROT": "Enchanted Carrot", 143 | "ENCHANTED_PUMPKIN": "Enchanted Pumpkin", 144 | "GREEN_CANDY": "Green Candy", 145 | "ENCHANTED_REDSTONE": "Enchanted Redstone", 146 | "ROTTEN_FLESH": "Rotten Flesh", 147 | "ENCHANTED_COOKED_FISH": "Enchanted Cooked Fish", 148 | "OBSIDIAN": "Obsidian", 149 | "ENCHANTED_MAGMA_CREAM": "Enchanted Magma Cream", 150 | "GRAVEL": "Gravel", 151 | "MELON": "Melon", 152 | "ENCHANTED_PACKED_ICE": "Enchanted Packed Ice", 153 | "RAW_FISH:3": "Pufferfish", 154 | "ENCHANTED_PRISMARINE_SHARD": "Enchanted Prismarine Shard", 155 | "ENCHANTED_IRON_BLOCK": "Enchanted Iron Block", 156 | "LEATHER": "Leather", 157 | "ENCHANTED_COOKED_MUTTON": "Enchanted Cooked Mutton", 158 | "BONE": "Bone", 159 | "RAW_FISH:1": "Salmon", 160 | "REVENANT_FLESH": "Revenant Flesh", 161 | "ENCHANTED_PORK": "Enchanted Pork", 162 | "ENCHANTED_GLOWSTONE": "Enchanted Glowstone", 163 | "ENCHANTED_RABBIT": "Enchanted Raw Rabbit", 164 | "ENCHANTED_BREAD": "Enchanted Bread", 165 | "FEATHER": "Feather", 166 | "ENCHANTED_CHARCOAL": "Enchanted Charcoal", 167 | "ENCHANTED_BLAZE_POWDER": "Enchanted Blaze Powder", 168 | "NETHERRACK": "Netherrack", 169 | "SUMMONING_EYE": "Summoning Eye", 170 | "SPONGE": "Sponge", 171 | "BLAZE_ROD": "Blaze Rod", 172 | "ENCHANTED_DARK_OAK_LOG": "Enchanted Dark Oak Log", 173 | "SNOW_BLOCK": "Snow Block", 174 | "ENCHANTED_BAKED_POTATO": "Enchanted Baked Potato", 175 | "COMPACTOR": "Compactor", 176 | "ENCHANTED_DIAMOND": "Enchanted Diamond", 177 | "ENCHANTED_GOLD": "Enchanted Gold", 178 | "WISE_FRAGMENT": "Wise Dragon Fragment", 179 | "PROTECTOR_FRAGMENT": "Protector Dragon Fragment", 180 | "STRONG_FRAGMENT": "Strong Dragon Fragment", 181 | "YOUNG_FRAGMENT": "Young Dragon Fragment", 182 | "UNSTABLE_FRAGMENT": "Unstable Dragon Fragment", 183 | "SUPERIOR_FRAGMENT": "Superior Dragon Fragment", 184 | "OLD_FRAGMENT": "Old Dragon Fragment", 185 | "ENCHANTED_FIREWORK_ROCKET": "Enchanted Firework Rocket", 186 | "ENCHANTED_LAVA_BUCKET": "Enchanted Lava Bucket", 187 | "CATALYST": "Catalyst", 188 | "ENCHANTED_REDSTONE_LAMP": "Enchanted Redstone Lamp", 189 | "ENCHANTED_HAY_BLOCK": "Enchanted Hay Bale", 190 | "HAY_BLOCK": "Hay Bale", 191 | "ENCHANTED_SEEDS": "Enchanted Seeds", 192 | "ENCHANTED_SAND": "Enchanted Sand", 193 | "ENCHANTED_RED_MUSHROOM": "Enchanted Red Mushroom", 194 | "ENCHANTED_BROWN_MUSHROOM": "Enchanted Brown Mushroom", 195 | "ENCHANTED_WET_SPONGE": "Enchanted Wet Sponge", 196 | "HAMSTER_WHEEL": "Hamster Wheel", 197 | "FOUL_FLESH": "Foul Flesh", 198 | "RECOMBOBULATOR_3000": "Recombobulator 3000", 199 | "HOLY_FRAGMENT": "Holy Dragon Fragment", 200 | "FUMING_POTATO_BOOK": "Fuming Potato Book", 201 | "CARROT_BAIT": "Carrot Bait", 202 | "MINNOW_BAIT": "Minnow Bait", 203 | "FISH_BAIT": "Fish Bait", 204 | "LIGHT_BAIT": "Light Bait", 205 | "DARK_BAIT": "Dark Bait", 206 | "SPOOKY_BAIT": "Spooky Bait", 207 | "SPIKED_BAIT": "Spiked Bait", 208 | "BLESSED_BAIT": "Blessed Bait", 209 | "ICE_BAIT": "Ice Bait", 210 | "WHALE_BAIT": "Whale Bait", 211 | "ENCHANTED_CLOWNFISH": "Enchanted Clownfish", 212 | "RAW_FISH:2": "Clownfish", 213 | "ENCHANTED_BONE_BLOCK": "Enchanted Bone Block", 214 | "BOOSTER_COOKIE": "Booster Cookie", 215 | "TIGER_SHARK_TOOTH": "Tiger Shark Tooth", 216 | "BLUE_SHARK_TOOTH": "Blue Shark Tooth", 217 | "NURSE_SHARK_TOOTH": "Nurse Shark Tooth", 218 | "GREAT_WHITE_SHARK_TOOTH": "Great White Shark Tooth", 219 | "SHARK_FIN": "Shark Fin", 220 | "ENCHANTED_SHARK_FIN": "Enchanted Shark Fin", 221 | "BAZAAR_COOKIE": "Bazaar Cookie", 222 | "GRIFFIN_FEATHER": "Griffin Feather", 223 | "DAEDALUS_STICK": "Daedalus Stick", 224 | "ANCIENT_CLAW": "Ancient Claw", 225 | "ENCHANTED_ANCIENT_CLAW": "Enchanted Ancient Claw", 226 | "REFINED_MINERAL": "Refined Mineral", 227 | "SHARK_BAIT": "Shark Bait", 228 | "HYPER_CATALYST": "Hyper Catalyst", 229 | "ECTOPLASM": "Ectoplasm", 230 | "PUMPKIN_GUTS": "Pumpkin Guts", 231 | "SPOOKY_SHARD": "Spooky Shard", 232 | "WEREWOLF_SKIN": "Werewolf Skin", 233 | "SOUL_FRAGMENT": "Soul Fragment", 234 | "JACOBS_TICKET": "Jacob's Ticket", 235 | "EXP_BOTTLE": "Experience Bottle", 236 | "GRAND_EXP_BOTTLE": "Grand Experience Bottle", 237 | "TITANIC_EXP_BOTTLE": "Titanic Experience Bottle", 238 | "COLOSSAL_EXP_BOTTLE": "Colossal Experience Bottle", 239 | "POLISHED_PUMPKIN": "Polished Pumpkin", 240 | "TIGHTLY_TIED_HAY_BALE": "Tightly-Tied Hay Bale", 241 | "MUTANT_NETHER_STALK": "Mutant Nether Wart", 242 | "REFINED_MITHRIL": "Refined Mithril", 243 | "TITANIUM_ORE": "Titanium", 244 | "MITHRIL_ORE": "Mithril", 245 | "STARFALL": "Starfall", 246 | "REFINED_TITANIUM": "Refined Titanium", 247 | "REFINED_DIAMOND": "Refined Diamond", 248 | "JERRY_BOX_GOLDEN": "Golden Jerry Box", 249 | "JERRY_BOX_PURPLE": "Purple Jerry Box", 250 | "JERRY_BOX_BLUE": "Blue Jerry Box", 251 | "JERRY_BOX_GREEN": "Green Jerry Box", 252 | "ENCHANTED_MITHRIL": "Enchanted Mithril", 253 | "ENCHANTED_TITANIUM": "Enchanted Titanium", 254 | "MAGMA_BUCKET": "Magma Bucket", 255 | "PLASMA_BUCKET": "Plasma Bucket", 256 | "DWARVEN_COMPACTOR": "Dwarven Super Compactor", 257 | "POWER_CRYSTAL": "Power Crystal", 258 | "TREASURITE": "Treasurite", 259 | "ENCHANTED_NETHERRACK": "Enchanted Netherrack", 260 | "NULL_SPHERE": "Null Sphere", 261 | "NULL_OVOID": "Null Ovoid", 262 | "NULL_ATOM": "Null Atom", 263 | "RAW_SOULFLOW": "Raw Soulflow", 264 | "SOULFLOW": "Soulflow", 265 | "ABSOLUTE_ENDER_PEARL": "Absolute Ender Pearl", 266 | "ROUGH_JADE_GEM": "\u2618 Rough Jade Gemstone", 267 | "FLAWED_JADE_GEM": "\u2618 Flawed Jade Gemstone", 268 | "FINE_JADE_GEM": "\u2618 Fine Jade Gemstone", 269 | "FLAWLESS_JADE_GEM": "\u2618 Flawless Jade Gemstone", 270 | "PERFECT_JADE_GEM": "\u2618 Perfect Jade Gemstone", 271 | "ROUGH_AMBER_GEM": "\u2E15 Rough Amber Gemstone", 272 | "FLAWED_AMBER_GEM": "\u2E15 Flawed Amber Gemstone", 273 | "FINE_AMBER_GEM": "\u2E15 Fine Amber Gemstone", 274 | "FLAWLESS_AMBER_GEM": "\u2E15 Flawless Amber Gemstone", 275 | "PERFECT_AMBER_GEM": "\u2E15 Perfect Amber Gemstone", 276 | "ROUGH_TOPAZ_GEM": "\u2727 Rough Topaz Gemstone", 277 | "FLAWED_TOPAZ_GEM": "\u2727 Flawed Topaz Gemstone", 278 | "FINE_TOPAZ_GEM": "\u2727 Fine Topaz Gemstone", 279 | "FLAWLESS_TOPAZ_GEM": "\u2727 Flawless Topaz Gemstone", 280 | "PERFECT_TOPAZ_GEM": "\u2727 Perfect Topaz Gemstone", 281 | "ROUGH_SAPPHIRE_GEM": "\u270E Rough Sapphire Gemstone", 282 | "FLAWED_SAPPHIRE_GEM": "\u270E Flawed Sapphire Gemstone", 283 | "FINE_SAPPHIRE_GEM": "\u270E Fine Sapphire Gemstone", 284 | "FLAWLESS_SAPPHIRE_GEM": "\u270E Flawless Sapphire Gemstone", 285 | "PERFECT_SAPPHIRE_GEM": "\u270E Perfect Sapphire Gemstone", 286 | "ROUGH_AMETHYST_GEM": "\u2748 Rough Amethyst Gemstone", 287 | "FLAWED_AMETHYST_GEM": "\u2748 Flawed Amethyst Gemstone", 288 | "FINE_AMETHYST_GEM": "\u2748 Fine Amethyst Gemstone", 289 | "FLAWLESS_AMETHYST_GEM": "\u2748 Flawless Amethyst Gemstone", 290 | "PERFECT_AMETHYST_GEM": "\u2748 Perfect Amethyst Gemstone", 291 | "ROUGH_JASPER_GEM": "\u2741 Rough Jasper Gemstone", 292 | "FLAWED_JASPER_GEM": "\u2741 Flawed Jasper Gemstone", 293 | "FINE_JASPER_GEM": "\u2741 Fine Jasper Gemstone", 294 | "FLAWLESS_JASPER_GEM": "\u2741 Flawless Jasper Gemstone", 295 | "PERFECT_JASPER_GEM": "\u2741 Perfect Jasper Gemstone", 296 | "ROUGH_RUBY_GEM": "\u2764 Rough Ruby Gemstone", 297 | "FLAWED_RUBY_GEM": "\u2764 Flawed Ruby Gemstone", 298 | "FINE_RUBY_GEM": "\u2764 Fine Ruby Gemstone", 299 | "FLAWLESS_RUBY_GEM": "\u2764 Flawless Ruby Gemstone", 300 | "PERFECT_RUBY_GEM": "\u2764 Perfect Ruby Gemstone", 301 | "ROUGH_OPAL_GEM": "\u2742 Rough Opal Gemstone", 302 | "FLAWED_OPAL_GEM": "\u2742 Flawed Opal Gemstone", 303 | "FINE_OPAL_GEM": "\u2742 Fine Opal Gemstone", 304 | "FLAWLESS_OPAL_GEM": "\u2742 Flawless Opal Gemstone", 305 | "PERFECT_OPAL_GEM": "\u2742 Perfect Opal Gemstone", 306 | "HARD_STONE": "Hard Stone", 307 | "ENCHANTED_HARD_STONE": "Enchanted Hard Stone", 308 | "CONCENTRATED_STONE": "Concentrated Stone", 309 | "YOGGIE": "Yoggie", 310 | "SLUDGE_JUICE": "Sludge Juice", 311 | "WORM_MEMBRANE": "Worm Membrane", 312 | "GOBLIN_EGG_GREEN": "Green Goblin Egg", 313 | "GOBLIN_EGG_BLUE": "Blue Goblin Egg", 314 | "GOBLIN_EGG_RED": "Red Goblin Egg", 315 | "GOBLIN_EGG_YELLOW": "Yellow Goblin Egg", 316 | "GOBLIN_EGG": "Goblin Egg", 317 | "ARACHNE_KEEPER_FRAGMENT": "Arachne's Keeper Fragment", 318 | "SOUL_STRING": "Soul String", 319 | "CHEESE_FUEL": "Tasty Cheese", 320 | "MYCEL": "Mycelium", 321 | "ENCHANTED_MYCELIUM": "Enchanted Mycelium", 322 | "ENCHANTED_MYCELIUM_CUBE": "Enchanted Mycelium Cube", 323 | "SULPHUR_ORE": "Sulphur", 324 | "ENCHANTED_SULPHUR": "Enchanted Sulphur", 325 | "ENCHANTED_SULPHUR_CUBE": "Enchanted Sulphur Cube", 326 | "SAND:1": "Red Sand", 327 | "ENCHANTED_RED_SAND": "Enchanted Red Sand", 328 | "ENCHANTED_RED_SAND_CUBE": "Enchanted Red Sand Cube", 329 | "CORRUPTED_BAIT": "Corrupted Bait", 330 | "HORNS_OF_TORMENT": "Horns Of Torment", 331 | "DARK_ORB": "Dark Orb", 332 | "ROCK_GEMSTONE": "Rock Gemstone", 333 | "END_STONE_SHULKER": "End Stone Shulker", 334 | "ENDER_MONOCLE": "Ender Monocle", 335 | "HAZMAT_ENDERMAN": "Hazmat Enderman", 336 | "LUXURIOUS_SPOOL": "Luxurious Spool", 337 | "KUUDRA_MANDIBLE": "Kuudra Mandible", 338 | "BLAZE_ROD_DISTILLATE": "Blaze Rod Distillate", 339 | "ACACIA_BIRDHOUSE": "Acacia Birdhouse", 340 | "DERELICT_ASHE": "Derelict Ashe", 341 | "MOIL_LOG": "Moil Log", 342 | "MAGMA_FISH_GOLD": "Gold Magmafish", 343 | "MELON_BLOCK": "Melon Block", 344 | "AMBER_MATERIAL": "Amber Material", 345 | "CRUDE_GABAGOOL_DISTILLATE": "Gabagool Distillate", 346 | "METEOR_SHARD": "Meteor Shard", 347 | "DIAMOND_ATOM": "Diamond Atom", 348 | "PRECURSOR_GEAR": "Precursor Gear", 349 | "NECROMANCER_BROOCH": "Necromancer Brooch", 350 | "DEEP_SEA_ORB": "Deep Sea Orb", 351 | "PETRIFIED_STARFALL": "Petrified Starfall", 352 | "RED_SCARF": "Red Scarf", 353 | "SADAN_BROOCH": "Sadan's Brooch", 354 | "ONYX": "Onyx", 355 | "OMEGA_EGG": "Omega Enchanted Egg", 356 | "MAGMA_CREAM_DISTILLATE": "Magma Cream Distillate", 357 | "FURBALL": "Furball", 358 | "GIANT_TOOTH": "Giant Tooth", 359 | "ENDSTONE_GEODE": "End Stone Geode", 360 | "OPTICAL_LENS": "Optical Lens", 361 | "HOT_BAIT": "Hot Bait", 362 | "BEATING_HEART": "Beating Heart", 363 | "AMALGAMATED_CRIMSONITE": "Amalgamated Crimsonite", 364 | "TOIL_LOG": "Toil Log", 365 | "SALMON_OPAL": "Salmon Opal", 366 | "AOTE_STONE": "Warped Stone", 367 | "GOLDEN_BALL": "Golden Ball", 368 | "MAGMA_FISH": "Magmafish", 369 | "GLOWSTONE_DUST_DISTILLATE": "Glowstone Distillate", 370 | "MANDRAA": "Mandraa", 371 | "MOLTEN_CUBE": "Molten Cube", 372 | "BULKY_STONE": "Bulky Stone", 373 | "INFERNO_VERTEX": "Inferno Vertex", 374 | "REFINED_AMBER": "Refined Amber", 375 | "JADERALD": "Jaderald", 376 | "NETHER_STALK_DISTILLATE": "Nether Wart Distillate", 377 | "RARE_DIAMOND": "Rare Diamond", 378 | "CRUDE_GABAGOOL": "Crude Gabagool", 379 | "DRAGON_SCALE": "Dragon Scale", 380 | "DRAGON_HORN": "Dragon Horn", 381 | "HARDENED_WOOD": "Hardened Wood", 382 | "HEAVY_GABAGOOL": "Heavy Gabagool", 383 | "INFERNO_APEX": "Inferno Apex", 384 | "ECCENTRIC_PAINTING": "Eccentric Painting", 385 | "REAPER_PEPPER": "Reaper Pepper", 386 | "DIAMONITE": "Diamonite", 387 | "SPIRIT_DECOY": "Spirit Stone", 388 | "HYPERGOLIC_GABAGOOL": "Hypergolic Gabagool", 389 | "OBSIDIAN_TABLET": "Obsidian Tablet", 390 | "VITAMIN_DEATH": "Vitamin Death", 391 | "CANDY_CORN": "Candy Corn", 392 | "MIDAS_JEWEL": "Midas Jewel", 393 | "PREMIUM_FLESH": "Premium Flesh", 394 | "PURE_MITHRIL": "Pure Mithril", 395 | "JERRY_STONE": "Jerry Stone", 396 | "MOLTEN_POWDER": "Molten Powder", 397 | "ROCK_CANDY": "Rock Candy", 398 | "MAGMA_URCHIN": "Magma Urchin", 399 | "SEARING_STONE": "Searing Stone", 400 | "BLESSED_FRUIT": "Blessed Fruit", 401 | "HOT_STUFF": "Hot Stuff", 402 | "SUSPICIOUS_VIAL": "Suspicious Vial", 403 | "DIRT_BOTTLE": "Dirt Bottle", 404 | "RED_NOSE": "Red Nose", 405 | "MAGMA_FISH_SILVER": "Silver Magmafish", 406 | "DRAGON_CLAW": "Dragon Claw", 407 | "LUCKY_DICE": "Lucky Dice", 408 | "FUEL_GABAGOOL": "Fuel Gabagool", 409 | "PRECIOUS_PEARL": "Precious Pearl", 410 | "SCORCHED_BOOKS": "Scorched Books", 411 | "MAGMA_FISH_DIAMOND": "Diamond Magmafish", 412 | "WITHER_BLOOD": "Wither Blood", 413 | "BLAZE_WAX": "Blaze Wax", 414 | "LAPIS_CRYSTAL": "Lapis Crystal", 415 | "SHINY_PRISM": "Shiny Prism" 416 | } 417 | -------------------------------------------------------------------------------- /discord-bot/src/bazaar/performance.js: -------------------------------------------------------------------------------- 1 | // Version 2.0 2 | 3 | module.exports = bazaarData => { 4 | bazaarData.forEach(product => { 5 | let diff = product.sellOrderPrice * 0.99 - product.buyOrderPrice; 6 | product.profitFlowPerMinute = ((product.sellCount + product.buyCount) === 0) 7 | ? 0 : (product.sellCount * product.buyCount) / (10080 8 | * (product.sellCount + product.buyCount)) * diff; 9 | }); 10 | 11 | bazaarData.sort((a, b) => b.profitFlowPerMinute - a.profitFlowPerMinute); 12 | return bazaarData; 13 | } 14 | 15 | /* 16 | 17 | VERSION 1.0 18 | 19 | module.exports = bazaarData => { 20 | bazaarData.sort((a, b) => { 21 | if (!a.buy || !a.sell) { 22 | return 1; 23 | } else if (!b.buy || !b.sell) { 24 | return -1; 25 | } 26 | 27 | return ( 28 | b.sell.pricePerUnit * 0.99 - 29 | b.buy.pricePerUnit - 30 | (a.sell.pricePerUnit * 0.99 - a.buy.pricePerUnit) 31 | ); 32 | }); 33 | let i = 1; 34 | bazaarData.forEach(data => { 35 | data.rankings = { 36 | diff: 176 - i++ 37 | }; 38 | }); 39 | i = 1; 40 | 41 | bazaarData.sort((a, b) => { 42 | if (!a.buy || !a.sell) { 43 | return 1; 44 | } else if (!b.buy || !b.sell) { 45 | return -1; 46 | } 47 | 48 | return ( 49 | (b.sell.pricePerUnit * 0.99) / b.buy.pricePerUnit - 50 | (a.sell.pricePerUnit * 0.99) / a.buy.pricePerUnit 51 | ); 52 | }); 53 | 54 | bazaarData.forEach(data => { 55 | data.rankings.change = 176 - i++; 56 | }); 57 | i = 1; 58 | 59 | bazaarData.sort((a, b) => { 60 | return ((b.buyCount + b.sellCount) / 2 - (a.buyCount + a.sellCount) / 2); 61 | }); 62 | 63 | bazaarData.forEach(data => { 64 | data.rankings.instants = 176 - i++; 65 | }); 66 | i = 1; 67 | 68 | return bazaarData; 69 | }; 70 | */ -------------------------------------------------------------------------------- /discord-bot/src/index.js: -------------------------------------------------------------------------------- 1 | const {Client} = require("discord.js"); 2 | const client = new Client({intents: 32767}); 3 | require('dotenv').config() 4 | 5 | const prefix = "~"; 6 | const channel = process.env.CHANNEL; 7 | client.on("ready", () => { 8 | const {cacheUpdate} = require("./bazaar/bazaar.js"); 9 | cacheUpdate(); 10 | console.log("Login successful!") 11 | }); 12 | 13 | client.login(process.env.BOT); 14 | 15 | module.exports = {client, prefix, channel}; -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | loom.platform=forge 2 | org.gradle.jvmargs=-Xmx2g 3 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/symt/BazaarNotifier/b4b089d210dac724b50f494cb781427f2366052b/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | ## 21 | ## Gradle start up script for UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | 86 | # Determine the Java command to use to start the JVM. 87 | if [ -n "$JAVA_HOME" ] ; then 88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 89 | # IBM's JDK on AIX uses strange locations for the executables 90 | JAVACMD="$JAVA_HOME/jre/sh/java" 91 | else 92 | JAVACMD="$JAVA_HOME/bin/java" 93 | fi 94 | if [ ! -x "$JAVACMD" ] ; then 95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 96 | 97 | Please set the JAVA_HOME variable in your environment to match the 98 | location of your Java installation." 99 | fi 100 | else 101 | JAVACMD="java" 102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 103 | 104 | Please set the JAVA_HOME variable in your environment to match the 105 | location of your Java installation." 106 | fi 107 | 108 | # Increase the maximum file descriptors if we can. 109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 110 | MAX_FD_LIMIT=`ulimit -H -n` 111 | if [ $? -eq 0 ] ; then 112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 113 | MAX_FD="$MAX_FD_LIMIT" 114 | fi 115 | ulimit -n $MAX_FD 116 | if [ $? -ne 0 ] ; then 117 | warn "Could not set maximum file descriptor limit: $MAX_FD" 118 | fi 119 | else 120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 121 | fi 122 | fi 123 | 124 | # For Darwin, add options to specify how the application appears in the dock 125 | if $darwin; then 126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 127 | fi 128 | 129 | # For Cygwin or MSYS, switch paths to Windows format before running java 130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 133 | 134 | JAVACMD=`cygpath --unix "$JAVACMD"` 135 | 136 | # We build the pattern for arguments to be converted via cygpath 137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 138 | SEP="" 139 | for dir in $ROOTDIRSRAW ; do 140 | ROOTDIRS="$ROOTDIRS$SEP$dir" 141 | SEP="|" 142 | done 143 | OURCYGPATTERN="(^($ROOTDIRS))" 144 | # Add a user-defined pattern to the cygpath arguments 145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 147 | fi 148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 149 | i=0 150 | for arg in "$@" ; do 151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 153 | 154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 156 | else 157 | eval `echo args$i`="\"$arg\"" 158 | fi 159 | i=`expr $i + 1` 160 | done 161 | case $i in 162 | 0) set -- ;; 163 | 1) set -- "$args0" ;; 164 | 2) set -- "$args0" "$args1" ;; 165 | 3) set -- "$args0" "$args1" "$args2" ;; 166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 172 | esac 173 | fi 174 | 175 | # Escape application args 176 | save () { 177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 178 | echo " " 179 | } 180 | APP_ARGS=`save "$@"` 181 | 182 | # Collect all arguments for the java command, following the shell quoting and substitution rules 183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 184 | 185 | exec "$JAVACMD" "$@" 186 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /log4j2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | mavenCentral() 4 | gradlePluginPortal() 5 | maven("https://oss.sonatype.org/content/repositories/snapshots") 6 | maven("https://maven.architectury.dev/") 7 | maven("https://maven.fabricmc.net") 8 | maven("https://maven.minecraftforge.net/") 9 | maven("https://repo.spongepowered.org/maven/") 10 | maven("https://repo.sk1er.club/repository/maven-releases/") 11 | } 12 | resolutionStrategy { 13 | eachPlugin { 14 | when (requested.id.id) { 15 | "gg.essential.loom" -> useModule("gg.essential:architectury-loom:${requested.version}") 16 | } 17 | } 18 | } 19 | } 20 | 21 | rootProject.name = "BazaarNotifier" 22 | -------------------------------------------------------------------------------- /src/main/java/dev/meyi/bn/BazaarNotifier.java: -------------------------------------------------------------------------------- 1 | package dev.meyi.bn; 2 | 3 | import com.google.common.collect.BiMap; 4 | import com.google.common.collect.HashBiMap; 5 | import com.google.gson.Gson; 6 | import com.google.gson.JsonObject; 7 | import com.google.gson.JsonSyntaxException; 8 | import com.google.gson.stream.JsonReader; 9 | import dev.meyi.bn.commands.BazaarNotifierCommand; 10 | import dev.meyi.bn.config.Configuration; 11 | import dev.meyi.bn.handlers.ChestTickHandler; 12 | import dev.meyi.bn.handlers.EventHandler; 13 | import dev.meyi.bn.handlers.MouseHandler; 14 | import dev.meyi.bn.handlers.UpdateHandler; 15 | import dev.meyi.bn.json.Order; 16 | import dev.meyi.bn.json.resp.BazaarResponse; 17 | import dev.meyi.bn.modules.Module; 18 | import dev.meyi.bn.modules.ModuleList; 19 | import dev.meyi.bn.utilities.ReflectionHelper; 20 | import dev.meyi.bn.utilities.ScheduledEvents; 21 | import dev.meyi.bn.utilities.Utils; 22 | import java.io.File; 23 | import java.io.IOException; 24 | import java.io.InputStreamReader; 25 | import java.io.StringReader; 26 | import java.nio.charset.StandardCharsets; 27 | import java.nio.file.Files; 28 | import java.nio.file.Paths; 29 | import java.security.KeyManagementException; 30 | import java.security.NoSuchAlgorithmException; 31 | import java.text.DecimalFormat; 32 | import java.util.ArrayList; 33 | import java.util.Objects; 34 | import net.minecraft.util.EnumChatFormatting; 35 | import net.minecraftforge.client.ClientCommandHandler; 36 | import net.minecraftforge.common.MinecraftForge; 37 | import net.minecraftforge.fml.common.Mod; 38 | import net.minecraftforge.fml.common.event.FMLInitializationEvent; 39 | import net.minecraftforge.fml.common.event.FMLLoadCompleteEvent; 40 | import net.minecraftforge.fml.common.event.FMLPreInitializationEvent; 41 | 42 | 43 | @Mod(modid = BazaarNotifier.MODID, version = BazaarNotifier.VERSION) 44 | public class BazaarNotifier { 45 | 46 | public static final String MODID = "BazaarNotifier"; 47 | public static final String VERSION = "1.7.5"; 48 | public static final String prefix = 49 | EnumChatFormatting.GOLD + "[" + EnumChatFormatting.YELLOW + "BN" + EnumChatFormatting.GOLD 50 | + "] " + EnumChatFormatting.RESET; 51 | public static final String header = 52 | EnumChatFormatting.GOLD + "" + EnumChatFormatting.BOLD + "" + EnumChatFormatting.STRIKETHROUGH 53 | + "-------" + EnumChatFormatting.RESET + "" + EnumChatFormatting.GOLD + "" 54 | + EnumChatFormatting.BOLD + " [ " + EnumChatFormatting.YELLOW + "BazaarNotifier" 55 | + EnumChatFormatting.GOLD + "" + EnumChatFormatting.BOLD + " ] " 56 | + EnumChatFormatting.STRIKETHROUGH + "-------"; 57 | public static final String RESOURCE_LOCATION = "https://raw.githubusercontent.com/symt/BazaarNotifier/resources/resources.json"; 58 | public static DecimalFormat df = new DecimalFormat("#,##0.0"); 59 | public static DecimalFormat dfNoDecimal = new DecimalFormat("#,###"); 60 | 61 | public static boolean activeBazaar = true; 62 | public static boolean inBazaar = false; 63 | public static boolean forceRender = false; 64 | 65 | 66 | public static ArrayList orders = new ArrayList<>(); 67 | public static BazaarResponse bazaarDataRaw; 68 | public static ModuleList modules; 69 | public static Configuration config; 70 | public static JsonObject resources; 71 | 72 | public static String guiToOpen = ""; 73 | 74 | public static JsonObject enchantCraftingList; 75 | public static BiMap bazaarConv = HashBiMap.create(); 76 | 77 | public static File resourcesFile; 78 | 79 | public static void resetMod() { 80 | modules.resetAll(); 81 | orders = new ArrayList<>(); 82 | } 83 | 84 | public static void resetScale() { 85 | modules.resetScale(); 86 | } 87 | 88 | @Mod.EventHandler 89 | public void preInit(FMLPreInitializationEvent event) { 90 | ReflectionHelper.setup(); 91 | File bnDir = new File(event.getModConfigurationDirectory(), "BazaarNotifier"); 92 | //noinspection ResultOfMethodCallIgnored 93 | bnDir.mkdirs(); 94 | resourcesFile = new File(bnDir, "resources.json"); 95 | Gson gson = new Gson(); 96 | 97 | try { 98 | if (resourcesFile.isFile()) { 99 | try { 100 | byte[] bytes = Files.readAllBytes(Paths.get(resourcesFile.getPath())); 101 | if (bytes.length == 0) throw new JsonSyntaxException("Invalid JSON in Resources File"); 102 | JsonReader resourcesReader = new JsonReader(new StringReader(new String( 103 | bytes, StandardCharsets.UTF_8))); 104 | resourcesReader.setLenient(true); 105 | resources = gson.fromJson(resourcesReader, JsonObject.class); 106 | } catch (JsonSyntaxException | ClassCastException e) { 107 | e.printStackTrace(); 108 | JsonReader reader = new JsonReader(new InputStreamReader(Objects.requireNonNull( 109 | BazaarNotifier.class.getResourceAsStream("/resources.json")), StandardCharsets.UTF_8)); 110 | reader.setLenient(true); 111 | resources = gson.fromJson(reader, JsonObject.class); 112 | } 113 | } else { 114 | JsonReader reader = new JsonReader(new InputStreamReader(Objects.requireNonNull( 115 | BazaarNotifier.class.getResourceAsStream("/resources.json")), StandardCharsets.UTF_8)); 116 | reader.setLenient(true); 117 | resources = gson.fromJson(reader, JsonObject.class); 118 | } 119 | } catch (IOException e) { 120 | e.printStackTrace(); 121 | } 122 | 123 | try { 124 | Utils.updateResources(); 125 | } catch (IOException | KeyManagementException | NoSuchAlgorithmException | ClassCastException e) { 126 | System.err.println("Error while getting resources from GitHub"); 127 | e.printStackTrace(); 128 | JsonObject bazaarConversions = resources.getAsJsonObject("bazaarConversions"); 129 | enchantCraftingList = resources.getAsJsonObject("enchantCraftingList"); 130 | bazaarConv = Utils.jsonToBimap(bazaarConversions); 131 | } 132 | } 133 | 134 | 135 | @Mod.EventHandler 136 | public void init(FMLInitializationEvent event) { 137 | MinecraftForge.EVENT_BUS.register(new EventHandler()); 138 | MinecraftForge.EVENT_BUS.register(new ChestTickHandler()); 139 | MinecraftForge.EVENT_BUS.register(new MouseHandler()); 140 | MinecraftForge.EVENT_BUS.register(new UpdateHandler()); 141 | ClientCommandHandler.instance.registerCommand(new BazaarNotifierCommand()); 142 | 143 | 144 | 145 | config = new Configuration(); 146 | 147 | 148 | Runtime.getRuntime() 149 | .addShutdownHook( 150 | new Thread( 151 | () -> { 152 | Utils.saveResources(resourcesFile, resources); 153 | //config.save(); 154 | })); 155 | 156 | } 157 | @Mod.EventHandler 158 | public void done(FMLLoadCompleteEvent e){ 159 | ScheduledEvents.create(); 160 | modules = new ModuleList(); 161 | if (BazaarNotifier.config.firstLoad){ 162 | for(Module m : modules){ 163 | m.setActive(true); 164 | m.position.setPosition(20, 20); 165 | m.showInChat = false; 166 | m.showInGuis = false; 167 | } 168 | BazaarNotifier.config.firstLoad = false; 169 | } 170 | } 171 | 172 | } 173 | -------------------------------------------------------------------------------- /src/main/java/dev/meyi/bn/commands/BazaarNotifierCommand.java: -------------------------------------------------------------------------------- 1 | package dev.meyi.bn.commands; 2 | 3 | import dev.meyi.bn.BazaarNotifier; 4 | import dev.meyi.bn.json.resp.BazaarItem; 5 | import dev.meyi.bn.modules.calc.BankCalculator; 6 | import dev.meyi.bn.modules.calc.CraftingCalculator; 7 | import dev.meyi.bn.modules.calc.SuggestionCalculator; 8 | import dev.meyi.bn.utilities.Utils; 9 | import java.io.IOException; 10 | import java.security.KeyManagementException; 11 | import java.security.NoSuchAlgorithmException; 12 | import java.util.ArrayList; 13 | import java.util.List; 14 | import net.minecraft.command.CommandBase; 15 | import net.minecraft.command.ICommandSender; 16 | import net.minecraft.entity.player.EntityPlayer; 17 | import net.minecraft.event.ClickEvent; 18 | import net.minecraft.event.ClickEvent.Action; 19 | import net.minecraft.util.BlockPos; 20 | import net.minecraft.util.ChatComponentText; 21 | import net.minecraft.util.EnumChatFormatting; 22 | import org.apache.commons.lang3.text.WordUtils; 23 | 24 | 25 | public class BazaarNotifierCommand extends CommandBase { 26 | 27 | private static long date = 0; // This can be set to System.currentTimeMillis, but it makes testing annoying 28 | 29 | @Override 30 | public List getCommandAliases() { 31 | return new ArrayList() { 32 | { 33 | add("bn"); 34 | } 35 | }; 36 | } 37 | 38 | 39 | @Override 40 | public String getCommandName() { 41 | return "bazaarnotifier"; 42 | } 43 | 44 | @Override 45 | public String getCommandUsage(ICommandSender sender) { 46 | return "/bazaarnotifier [subcommand]"; 47 | } 48 | 49 | @Override 50 | public void processCommand(ICommandSender ics, String[] args) { 51 | if (ics instanceof EntityPlayer) { 52 | EntityPlayer player = (EntityPlayer) ics; 53 | if (args.length > 0 && args[0].equalsIgnoreCase("reset")) { 54 | if (args.length == 1 || args[1].equalsIgnoreCase("all")) { 55 | BazaarNotifier.resetMod(); 56 | player.addChatMessage(new ChatComponentText(BazaarNotifier.prefix + EnumChatFormatting.RED 57 | + "All module locations have been reset and the order list has been emptied.")); 58 | } else if (args[1].equalsIgnoreCase("orders") && args.length == 2) { 59 | BazaarNotifier.orders.clear(); 60 | player.addChatMessage(new ChatComponentText(BazaarNotifier.prefix + EnumChatFormatting.RED 61 | + "Your orders have been cleared.")); 62 | } else if (args[1].equalsIgnoreCase("scale") && args.length == 2) { 63 | BazaarNotifier.resetScale(); 64 | player.addChatMessage(new ChatComponentText(BazaarNotifier.prefix + EnumChatFormatting.RED 65 | + "Your scale for every module has been reset.")); 66 | } else if (args[1].equalsIgnoreCase("bank") && args.length == 2) { 67 | BankCalculator.reset(); 68 | player.addChatMessage(new ChatComponentText(BazaarNotifier.prefix + EnumChatFormatting.RED 69 | + "Your bank module has been reset.")); 70 | } else { 71 | player.addChatMessage(new ChatComponentText(BazaarNotifier.prefix + EnumChatFormatting.RED 72 | + "That module doesn't exist.")); 73 | } 74 | } else if (args.length >= 1 && args[0].equalsIgnoreCase("find")) { 75 | if (args.length == 1) { 76 | player.addChatMessage(new ChatComponentText(BazaarNotifier.prefix + EnumChatFormatting.RED 77 | + "Use the following format: /bn find (item)")); 78 | } else { 79 | String itemName = WordUtils 80 | .capitalize(String.join(" ", args).substring(5).replaceAll("-", " ")); 81 | if (BazaarNotifier.bazaarDataRaw != null) { 82 | String[] itemSet = Utils.getItemIdFromName(itemName); 83 | itemName = itemSet[0]; 84 | String itemConv = itemSet[1]; 85 | BazaarItem item = BazaarNotifier.bazaarDataRaw.products.get(itemConv); 86 | String findItemString = BazaarNotifier.prefix + EnumChatFormatting.RED 87 | + "Please provide a valid item to find."; 88 | 89 | String bulletPoint = EnumChatFormatting.WHITE + "\u2022 "; 90 | String separator = EnumChatFormatting.RED + " / "; 91 | 92 | if (BazaarNotifier.bazaarConv.containsKey(itemConv)) { 93 | findItemString = BazaarNotifier.header + "\n" + 94 | EnumChatFormatting.DARK_RED + EnumChatFormatting.BOLD + WordUtils 95 | .capitalize(itemName) + "\n " + bulletPoint + 96 | EnumChatFormatting.RED + "Buy Order: " + 97 | EnumChatFormatting.GRAY + BazaarNotifier.df.format(item.sell_summary.size() == 0 ? 98 | 0 : item.sell_summary.get(0).pricePerUnit) + "\n " + bulletPoint + 99 | EnumChatFormatting.RED + "Sell Offer: " + 100 | EnumChatFormatting.GRAY + BazaarNotifier.df.format(item.buy_summary.size() == 0 ? 101 | 0 : item.buy_summary.get(0).pricePerUnit) + "\n " + bulletPoint + 102 | EnumChatFormatting.RED + "Estimated Profit: " + 103 | EnumChatFormatting.GRAY + BazaarNotifier.df 104 | .format(SuggestionCalculator.calculateEP(item)) + "\n"; 105 | } 106 | 107 | if (BazaarNotifier.enchantCraftingList.getAsJsonObject("other").has(itemConv)) { 108 | 109 | String[] prices = CraftingCalculator.getEnchantCraft(itemConv); 110 | 111 | findItemString += 112 | EnumChatFormatting.DARK_RED + "" + EnumChatFormatting.BOLD + "Crafting (" + 113 | EnumChatFormatting.GRAY 114 | + "Buy order" + separator + EnumChatFormatting.GRAY + "Instant buy" 115 | + EnumChatFormatting.DARK_RED + EnumChatFormatting.BOLD + ")" + "\n " + 116 | bulletPoint + 117 | EnumChatFormatting.RED + "Profit (Instant Sell): " + 118 | EnumChatFormatting.GRAY + BazaarNotifier.df 119 | .format(Double.parseDouble(prices[0])) + separator + EnumChatFormatting.GRAY + 120 | BazaarNotifier.df.format(Double.parseDouble(prices[3])) + "\n " + bulletPoint 121 | + 122 | EnumChatFormatting.RED + "Profit (Sell Offer): " + 123 | EnumChatFormatting.GRAY + BazaarNotifier.df 124 | .format(Double.parseDouble(prices[1])) + separator + EnumChatFormatting.GRAY + 125 | BazaarNotifier.df.format(Double.parseDouble(prices[4])) + "\n " + bulletPoint 126 | + 127 | EnumChatFormatting.RED + "Profit per 1M: " + 128 | EnumChatFormatting.GRAY + BazaarNotifier.df 129 | .format(Double.parseDouble(prices[2])) + separator + EnumChatFormatting.GRAY + 130 | BazaarNotifier.df.format(Double.parseDouble(prices[5])) + "\n" + 131 | BazaarNotifier.header; 132 | 133 | } else if (BazaarNotifier.bazaarConv.containsKey(itemConv)) { 134 | findItemString += BazaarNotifier.header; 135 | } 136 | 137 | player.addChatMessage(new ChatComponentText(findItemString)); 138 | 139 | } else { 140 | player.addChatMessage(new ChatComponentText( 141 | BazaarNotifier.prefix + EnumChatFormatting.RED 142 | + "Please wait a moment for the mod to get bazaar information")); 143 | } 144 | } 145 | } else if (args.length == 1 && args[0].equalsIgnoreCase("help")) { 146 | player.addChatMessage(new ChatComponentText( 147 | BazaarNotifier.header + "\n" 148 | + EnumChatFormatting.RED + "/bn " + EnumChatFormatting.DARK_RED + "\u2192" 149 | + EnumChatFormatting.GRAY + " Opens the GUI\n" 150 | + EnumChatFormatting.RED + "/bn reset (value) " + EnumChatFormatting.DARK_RED 151 | + "\u2192" + EnumChatFormatting.GRAY 152 | + " Reset specific modules to default settings\n" 153 | + EnumChatFormatting.RED + "/bn api (key) " + EnumChatFormatting.DARK_RED + "\u2192" 154 | + EnumChatFormatting.GRAY + " Sets your api key for crafting module\n" 155 | + EnumChatFormatting.RED + "/bn find (item) " + EnumChatFormatting.DARK_RED 156 | + "\u2192" + EnumChatFormatting.GRAY + " Search specific item's prices and EP\n" 157 | + EnumChatFormatting.RED + "/bn discord " + EnumChatFormatting.DARK_RED + "\u2192" 158 | + EnumChatFormatting.GRAY + " Provides discord link (beta access + more)\n" 159 | + BazaarNotifier.header 160 | )); 161 | } else if (args.length == 1 && args[0].equalsIgnoreCase("__force")) { 162 | BazaarNotifier.forceRender ^= true; 163 | player.addChatMessage(new ChatComponentText(BazaarNotifier.prefix + EnumChatFormatting.RED 164 | + "This command is intended for testing purposes only, use it at your own peril. Forced rendering has been turned " 165 | + EnumChatFormatting.DARK_RED + (BazaarNotifier.forceRender ? "on" : "off"))); 166 | } else if (args.length == 1 && args[0].equalsIgnoreCase("discord")) { 167 | ChatComponentText discordLink = new ChatComponentText( 168 | EnumChatFormatting.DARK_GREEN + "" + EnumChatFormatting.BOLD 169 | + "[DISCORD LINK]"); 170 | discordLink 171 | .setChatStyle(discordLink.getChatStyle().setChatClickEvent(new ClickEvent( 172 | Action.OPEN_URL, 173 | "https://discord.com/invite/wjpJSVSwvD"))); 174 | ChatComponentText supportLink = new ChatComponentText( 175 | EnumChatFormatting.DARK_GREEN + "" + EnumChatFormatting.BOLD 176 | + "[PATREON LINK]"); 177 | supportLink 178 | .setChatStyle(supportLink.getChatStyle().setChatClickEvent(new ClickEvent( 179 | Action.OPEN_URL, 180 | "https://patreon.com/meyi"))); 181 | 182 | player.addChatMessage(new ChatComponentText( 183 | BazaarNotifier.prefix + "\n" + EnumChatFormatting.GREEN + "Join the discord server: ") 184 | .appendSibling(discordLink).appendSibling( 185 | new ChatComponentText( 186 | "\n" + EnumChatFormatting.GREEN + "If you want, you can support my work: ") 187 | .appendSibling(supportLink)) 188 | .appendSibling(new ChatComponentText("\n" + BazaarNotifier.prefix))); 189 | 190 | } else if (args.length == 1 && args[0].equalsIgnoreCase("update")) { 191 | if (date < System.currentTimeMillis() - (10 * 60 * 1000)) { 192 | new Thread(() -> { 193 | try { 194 | Utils.updateResources(); 195 | date = System.currentTimeMillis(); 196 | } catch (IOException | KeyManagementException | NoSuchAlgorithmException | ClassCastException e) { 197 | player.addChatMessage(new ChatComponentText( 198 | BazaarNotifier.prefix + EnumChatFormatting.RED 199 | + "Resource update failed. Please try again.")); 200 | } 201 | }).start(); 202 | player.addChatMessage( 203 | new ChatComponentText(BazaarNotifier.prefix + EnumChatFormatting.GREEN 204 | + "Updating required resources from GitHub")); 205 | 206 | } else { 207 | player.addChatMessage(new ChatComponentText(BazaarNotifier.prefix + EnumChatFormatting.RED 208 | + "Please wait 10 minutes before running that command again")); 209 | } 210 | } else if (args.length > 0) { 211 | player.addChatMessage(new ChatComponentText(BazaarNotifier.prefix + EnumChatFormatting.RED 212 | + "The command you just tried to do doesn't exist. Do /bn")); 213 | } else { 214 | BazaarNotifier.guiToOpen = "settings"; 215 | } 216 | } 217 | } 218 | 219 | public boolean canCommandSenderUseCommand(final ICommandSender sender) { 220 | return true; 221 | } 222 | 223 | 224 | @Override 225 | public List addTabCompletionOptions(ICommandSender sender, String[] args, BlockPos pos) { 226 | List arguments = new ArrayList<>(); 227 | 228 | if (args.length <= 1) { 229 | new ArrayList() { 230 | { 231 | add("discord"); 232 | add("find"); 233 | add("reset"); 234 | add("update"); 235 | } 236 | }.forEach(cmd -> { 237 | if (args[0].trim().length() == 0 || cmd.startsWith(args[0].toLowerCase())) { 238 | arguments.add(cmd); 239 | } 240 | }); 241 | } else { 242 | if (args.length <= 2 && args[0].equalsIgnoreCase("reset")) { 243 | new ArrayList() { 244 | { 245 | add("all"); 246 | add("orders"); 247 | add("scale"); 248 | add("bank"); 249 | } 250 | }.forEach(cmd -> { 251 | if (args[1].trim().length() == 0 || cmd.startsWith(args[1].toLowerCase())) { 252 | arguments.add(cmd); 253 | } 254 | }); 255 | } else if (args.length <= 2 && args[0].equalsIgnoreCase("find")) { 256 | ArrayList a = new ArrayList<>(); 257 | for (String s : BazaarNotifier.bazaarConv.values()) { 258 | s = s.replace(' ', '-'); 259 | a.add(s.toLowerCase()); 260 | } 261 | a.forEach(cmd -> { 262 | if (args[1].trim().length() == 0 || cmd.startsWith(args[1].toLowerCase())) { 263 | arguments.add(cmd); 264 | } 265 | if (!arguments.contains(cmd) && (cmd.contains(args[1].toLowerCase()))) { 266 | arguments.add(cmd); 267 | } 268 | }); 269 | } 270 | } 271 | return arguments; 272 | } 273 | } -------------------------------------------------------------------------------- /src/main/java/dev/meyi/bn/config/Configuration.java: -------------------------------------------------------------------------------- 1 | package dev.meyi.bn.config; 2 | 3 | import cc.polyfrost.oneconfig.config.Config; 4 | import cc.polyfrost.oneconfig.config.annotations.Button; 5 | import cc.polyfrost.oneconfig.config.annotations.HUD; 6 | import cc.polyfrost.oneconfig.config.annotations.Switch; 7 | import cc.polyfrost.oneconfig.config.core.OneColor; 8 | import cc.polyfrost.oneconfig.config.data.Mod; 9 | import cc.polyfrost.oneconfig.config.data.ModType; 10 | import cc.polyfrost.oneconfig.config.migration.JsonMigrator; 11 | import cc.polyfrost.oneconfig.config.migration.JsonName; 12 | import dev.meyi.bn.BazaarNotifier; 13 | import dev.meyi.bn.modules.calc.BankCalculator; 14 | import dev.meyi.bn.modules.module.BankModule; 15 | import dev.meyi.bn.modules.module.CraftingModule; 16 | import dev.meyi.bn.modules.module.NotificationModule; 17 | import dev.meyi.bn.modules.module.SuggestionModule; 18 | import dev.meyi.bn.utilities.Defaults; 19 | 20 | import java.awt.Color; 21 | 22 | 23 | public class Configuration extends Config { 24 | 25 | public Configuration() { 26 | super(new Mod("BazaarNotifier", ModType.SKYBLOCK,"/icon.png", new JsonMigrator("./config/BazaarNotifier/config.json")), "bazaarnotifier.json"); 27 | initialize(); 28 | 29 | addListener("collectionCheck", () -> { 30 | collectionCheck = false; 31 | }); 32 | 33 | collectionCheck = false; // in case the user has it enabled already, we want to force it off 34 | } 35 | 36 | public boolean firstLoad = true; 37 | 38 | @JsonName("version") 39 | public String version = BazaarNotifier.VERSION; 40 | 41 | @HUD(name = "Suggestion Module", 42 | category = "Suggestion Module" 43 | ) 44 | public SuggestionModule suggestionModule = new SuggestionModule(); 45 | @HUD(name = "Crafting Module", 46 | category = "Crafting Module" 47 | ) 48 | public CraftingModule craftingModule = new CraftingModule(); 49 | @HUD(name = "Notification Module", 50 | category = "Notification Module" 51 | ) 52 | public NotificationModule notificationModule = new NotificationModule(); 53 | @HUD(name = "Bank Module", 54 | category = "Bank Module" 55 | ) 56 | public BankModule bankModule = new BankModule(); 57 | 58 | @Switch(name = "Allow old Movement and Rescaling", 59 | category = "General", 60 | description = "Allows movement and rescaling outside the edit hud window" 61 | ) 62 | public boolean legacyMovement = true; 63 | @JsonName("showChatMessages") 64 | @Switch(name = "Show Chat Messages", 65 | description = "Disables messages from Bazaar Notifier" 66 | ) 67 | public boolean showChatMessages = Defaults.SEND_CHAT_MESSAGES; 68 | 69 | @JsonName("collectionCheck") 70 | @Switch(name = "Collection checks", 71 | category = "Crafting Module", 72 | description = "Only shows unlocked recipes" 73 | ) 74 | public boolean collectionCheck = Defaults.COLLECTION_CHECKING; 75 | 76 | 77 | @cc.polyfrost.oneconfig.config.annotations.Color( 78 | name = "Info Color", 79 | allowAlpha = false, 80 | description = "The color for all information and less significant elements" 81 | ) 82 | public OneColor infoColor = new OneColor(Color.GRAY); 83 | @cc.polyfrost.oneconfig.config.annotations.Color(name = "Item Color", 84 | allowAlpha = false, 85 | description = "The color the items will be in" 86 | ) 87 | public OneColor itemColor = new OneColor(Color.CYAN); 88 | @cc.polyfrost.oneconfig.config.annotations.Color( 89 | name = "Number Color", 90 | allowAlpha = false, 91 | description = "The color the numbers will be in" 92 | ) 93 | public OneColor numberColor = new OneColor(Color.MAGENTA); 94 | 95 | @SuppressWarnings("unused") 96 | @Button(name = "Reset Colors", text = "Reset") 97 | Runnable r = () ->{ 98 | infoColor = new OneColor(Color.GRAY); 99 | itemColor = new OneColor(Color.CYAN); 100 | numberColor = new OneColor(Color.MAGENTA); 101 | }; 102 | 103 | 104 | @SuppressWarnings("unused") 105 | @Button( 106 | name = "Reset Profit", 107 | text = "Reset", 108 | category = "Bank Module" 109 | ) 110 | Runnable resetBank = BankCalculator::reset; 111 | 112 | @JsonName("lastLogin") 113 | public long lastLogin = System.currentTimeMillis(); 114 | } 115 | -------------------------------------------------------------------------------- /src/main/java/dev/meyi/bn/handlers/ChestTickHandler.java: -------------------------------------------------------------------------------- 1 | package dev.meyi.bn.handlers; 2 | 3 | import dev.meyi.bn.BazaarNotifier; 4 | import dev.meyi.bn.json.Order; 5 | import dev.meyi.bn.utilities.ReflectionHelper; 6 | import dev.meyi.bn.utilities.Utils; 7 | import java.util.Arrays; 8 | import java.util.List; 9 | import java.util.regex.Matcher; 10 | import java.util.regex.Pattern; 11 | import net.minecraft.client.Minecraft; 12 | import net.minecraft.client.gui.inventory.GuiChest; 13 | import net.minecraft.inventory.IInventory; 14 | import net.minecraft.item.Item; 15 | import net.minecraft.item.ItemStack; 16 | import net.minecraft.util.ChatComponentText; 17 | import net.minecraft.util.EnumChatFormatting; 18 | import net.minecraft.util.StringUtils; 19 | import net.minecraftforge.client.event.GuiScreenEvent; 20 | import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; 21 | import net.minecraftforge.fml.common.gameevent.TickEvent; 22 | import net.minecraftforge.fml.common.gameevent.TickEvent.Phase; 23 | 24 | @SuppressWarnings("unused") 25 | public class ChestTickHandler { 26 | 27 | public static String lastScreenDisplayName = ""; 28 | 29 | // /blockdata x y z {CustomName:"___"} << For Custom Chest Name Testing 30 | 31 | public static void updateBazaarOrders(IInventory chest) { 32 | int[] verifiedOrders = new int[BazaarNotifier.orders.size()]; 33 | ItemStack[] items = new ItemStack[chest.getSizeInventory() + 1]; 34 | for (int i = 0; i < chest.getSizeInventory(); i++) { 35 | items[i] = chest.getStackInSlot(i); 36 | } 37 | items[items.length - 1] = Minecraft.getMinecraft().thePlayer.inventory.getItemStack(); 38 | 39 | Pattern p = Pattern.compile("(?:\\[.*\\] )(.*)"); 40 | Matcher m = p.matcher(StringUtils.stripControlCodes(Minecraft.getMinecraft().thePlayer.getDisplayName().getUnformattedText())); 41 | String playerName = null; 42 | if (m.find()) { 43 | playerName = m.group(1); 44 | } else { 45 | System.err.println("Improperly formatted player name. Aborting!"); 46 | } 47 | 48 | for (ItemStack item : items) { 49 | if(item == null) continue; 50 | int itemID = Item.itemRegistry.getIDForObject(item.getItem()); 51 | if ( itemID == 160 // Glass 52 | || itemID == 102 // Glass 53 | || itemID == 262 // Arrow 54 | || (itemID == 154 && StringUtils.stripControlCodes(item.getDisplayName()).equals("Claim All Coins"))) { //Hopper 55 | continue; 56 | } //Hopper 57 | List lore = Utils.getLoreFromItemStack(item); 58 | 59 | Pattern p2 = Pattern.compile("(BUY|SELL):? (.*)"); 60 | Matcher m2 = p2.matcher(StringUtils.stripControlCodes(item.getDisplayName())); 61 | String displayName; 62 | Order.OrderType type; 63 | if (m2.find()) { 64 | displayName = m2.group(2); 65 | type = "sell".equalsIgnoreCase(m2.group(1)) ? Order.OrderType.SELL : Order.OrderType.BUY; 66 | } else { 67 | System.err.println("Bazaar item header incorrect. Aborting!"); 68 | return; 69 | } 70 | 71 | if (BazaarNotifier.bazaarConv.containsValue(displayName)) { 72 | String priceString; 73 | if (lore.get(4).toLowerCase().contains("expire")) { 74 | priceString = StringUtils.stripControlCodes(lore.get(6)).replaceAll(",", "") 75 | .split(" ")[3]; 76 | } else if (lore.get(5).toLowerCase().contains("expire")) { 77 | priceString = StringUtils.stripControlCodes(lore.get(7)).replaceAll(",", "") 78 | .split(" ")[3]; 79 | } else { 80 | priceString = StringUtils.stripControlCodes( 81 | lore.get((lore.get(3).startsWith("Filled:")) ? 5 : 4).replaceAll(",", "") 82 | .split(" ")[3]); 83 | } 84 | int orderInQuestion = -1; 85 | for (int j = 0; j < BazaarNotifier.orders.size(); j++) { 86 | Order order = BazaarNotifier.orders.get(j); 87 | if (priceString.equalsIgnoreCase(order.priceString) && type.equals( 88 | order.type)) { // Todo check product also causing problems 89 | orderInQuestion = j; 90 | break; 91 | } 92 | } 93 | if (orderInQuestion != -1) { 94 | verifiedOrders[orderInQuestion] = 1; 95 | Order order = BazaarNotifier.orders.get(orderInQuestion); 96 | int totalAmount = order.startAmount; 97 | int amountLeft = Utils.getOrderAmountLeft(lore, totalAmount); 98 | if (amountLeft > 0) { 99 | order.setAmountRemaining(amountLeft); 100 | } 101 | } else if (playerName != null) { 102 | Pattern p3 = Pattern.compile("By: (?:\\[.*\\] )?(.*)"); 103 | String creator = ""; 104 | for (String line : lore) { 105 | Matcher m3 = p3.matcher(line); 106 | if (m3.find()) { 107 | creator = m3.group(1); 108 | break; 109 | } 110 | } 111 | if (creator.equals(playerName) || creator.isEmpty()) { //isEmpty for non Coop Islands 112 | if (lore.get(4).toLowerCase().contains("expire") || lore.get(5).toLowerCase().contains("expire")) { 113 | continue; 114 | } 115 | String totalAmount = lore.get(2).split(" ")[2]; 116 | int startAmount = Integer.parseInt(totalAmount.substring(0, totalAmount.length()-1).replace(",", "")); 117 | Order newOrder = new Order(displayName, startAmount, Double.parseDouble(priceString), priceString, type); 118 | newOrder.setAmountRemaining(Utils.getOrderAmountLeft(lore, startAmount)); 119 | if (newOrder.getAmountRemaining() != 0) { 120 | BazaarNotifier.orders.add(newOrder); 121 | verifiedOrders = Arrays.copyOf(verifiedOrders, verifiedOrders.length + 1); 122 | verifiedOrders[verifiedOrders.length-1] = 1; 123 | } 124 | } 125 | } 126 | } else { 127 | System.out.println(BazaarNotifier.orders); 128 | System.err.println("Some orders weren't found! Bad display name: " + displayName); 129 | return; 130 | } 131 | } 132 | for (int i = verifiedOrders.length - 1; i >= 0; i--) { 133 | if (verifiedOrders[i] == 0) { 134 | BazaarNotifier.orders.remove(i); 135 | } 136 | } 137 | } 138 | 139 | @SubscribeEvent 140 | public void onChestTick(TickEvent e) { 141 | try { 142 | if (e.phase == Phase.END) { 143 | if (Minecraft.getMinecraft().currentScreen instanceof GuiChest && BazaarNotifier.inBazaar 144 | && BazaarNotifier.activeBazaar) { 145 | 146 | IInventory chest = ReflectionHelper.getLowerChestInventory( 147 | (GuiChest) Minecraft.getMinecraft().currentScreen); 148 | if (chest == null) { 149 | return; 150 | } 151 | String chestName = chest.getDisplayName().getUnformattedText().toLowerCase(); 152 | 153 | if (chest.hasCustomName() && !lastScreenDisplayName.equalsIgnoreCase(chestName)) { 154 | if (chestName.equals("confirm buy order") || chestName.equals("confirm sell offer")) { 155 | if (chest.getStackInSlot(13) != null) { 156 | if (orderConfirmation(chest)) { // Don´t reset the lastScreenDisplayName to retry in the next tick 157 | lastScreenDisplayName = StringUtils.stripControlCodes( 158 | chest.getDisplayName().getUnformattedText()); 159 | } 160 | } 161 | 162 | } else if (chestName.contains("bazaar orders")) { 163 | if (chest.getStackInSlot(chest.getSizeInventory() - 5) != null && 164 | Item.itemRegistry.getIDForObject( 165 | chest.getStackInSlot(chest.getSizeInventory() - 5).getItem()) == 262) { 166 | lastScreenDisplayName = StringUtils.stripControlCodes( 167 | chest.getDisplayName().getUnformattedText()); 168 | updateBazaarOrders(chest); 169 | } 170 | } else if (chestName.contains("bazaar")) { 171 | lastScreenDisplayName = StringUtils.stripControlCodes( 172 | chest.getDisplayName().getUnformattedText()); 173 | } 174 | } 175 | } else if (!BazaarNotifier.inBazaar) { // if you aren't in the bazaar, this should be clear 176 | ChestTickHandler.lastScreenDisplayName = ""; 177 | } 178 | } 179 | }catch (Exception ex){ 180 | Minecraft.getMinecraft().thePlayer.addChatMessage( 181 | new ChatComponentText(BazaarNotifier.prefix + EnumChatFormatting.RED + 182 | "The onChestTick method ran into an error. Please report this in the discord server")); 183 | ex.printStackTrace(); 184 | } 185 | } 186 | 187 | /** 188 | * @return success 189 | */ 190 | private boolean orderConfirmation(IInventory chest) { 191 | 192 | if (chest.getStackInSlot(13) != null) { 193 | String priceString = ""; 194 | String product = ""; 195 | double price = 0; 196 | 197 | try { 198 | priceString = StringUtils.stripControlCodes( 199 | chest.getStackInSlot(13).getTagCompound().getCompoundTag("display").getTagList("Lore", 8) 200 | .getStringTagAt(2)).split(" ")[3].replaceAll(",", ""); 201 | price = Double.parseDouble(priceString); 202 | 203 | product = StringUtils.stripControlCodes( 204 | chest.getStackInSlot(13).getTagCompound().getCompoundTag("display").getTagList("Lore", 8) 205 | .getStringTagAt(4)).split("x ", 2)[1]; 206 | }catch (Exception e){ 207 | return false; 208 | } 209 | String productName; 210 | 211 | if (!BazaarNotifier.bazaarConv.containsValue(product)) { 212 | String[] possibleConversion = Utils.getItemIdFromName(product); 213 | productName = possibleConversion[1]; 214 | 215 | if (!possibleConversion[0].equals(product) && !productName.isEmpty()) { 216 | BazaarNotifier.bazaarConv.put(productName, product); 217 | Minecraft.getMinecraft().thePlayer.addChatMessage(new ChatComponentText( 218 | BazaarNotifier.prefix + EnumChatFormatting.RED 219 | + "A possible conversion was found. Please report this to the discord server:" 220 | + EnumChatFormatting.GRAY + " \"" 221 | + productName + "\" - \"" + product + "\" \"" + possibleConversion[0] + "\".")); 222 | } else { 223 | Minecraft.getMinecraft().thePlayer.addChatMessage(new ChatComponentText( 224 | BazaarNotifier.prefix + EnumChatFormatting.RED 225 | + "No item conversion was found for that item. Please report this to the discord server: " 226 | + EnumChatFormatting.GRAY + "\"" 227 | + product + "\".")); 228 | } 229 | } else { 230 | productName = BazaarNotifier.bazaarConv.inverse().get(product); 231 | } 232 | 233 | if (BazaarNotifier.bazaarConv.containsKey(productName)) { 234 | String productWithAmount = StringUtils.stripControlCodes( 235 | chest.getStackInSlot(13).getTagCompound().getCompoundTag("display") 236 | .getTagList("Lore", 8).getStringTagAt(4)).split(": ")[1]; 237 | 238 | int amount = Integer.parseInt(StringUtils.stripControlCodes( 239 | chest.getStackInSlot(13).getTagCompound().getCompoundTag("display") 240 | .getTagList("Lore", 8).getStringTagAt(4)).split(": ")[1].split("x ")[0].replaceAll( 241 | ",", "")); 242 | 243 | EventHandler.productVerify[0] = productName; 244 | EventHandler.productVerify[1] = productWithAmount; 245 | Order.OrderType type = 246 | StringUtils.stripControlCodes(chest.getDisplayName().getUnformattedText()) 247 | .equalsIgnoreCase("Confirm Sell Offer") ? Order.OrderType.SELL 248 | : Order.OrderType.BUY; 249 | EventHandler.verify = new Order(product, amount, price, priceString, type); 250 | } 251 | } 252 | return true; 253 | } 254 | @SubscribeEvent 255 | public void renderInChest(GuiScreenEvent.BackgroundDrawnEvent e){ 256 | BazaarNotifier.modules.drawAllGui(); 257 | } 258 | } -------------------------------------------------------------------------------- /src/main/java/dev/meyi/bn/handlers/EventHandler.java: -------------------------------------------------------------------------------- 1 | package dev.meyi.bn.handlers; 2 | 3 | import cc.polyfrost.oneconfig.gui.OneConfigGui; 4 | import cc.polyfrost.oneconfig.gui.pages.ModConfigPage; 5 | import cc.polyfrost.oneconfig.platform.Platform; 6 | import dev.meyi.bn.BazaarNotifier; 7 | import dev.meyi.bn.json.Order; 8 | import dev.meyi.bn.modules.calc.BankCalculator; 9 | import dev.meyi.bn.utilities.ReflectionHelper; 10 | import net.minecraft.client.Minecraft; 11 | import net.minecraft.client.gui.inventory.GuiChest; 12 | import net.minecraft.client.gui.inventory.GuiEditSign; 13 | import net.minecraft.inventory.IInventory; 14 | import net.minecraft.util.StringUtils; 15 | import net.minecraftforge.client.event.ClientChatReceivedEvent; 16 | import net.minecraftforge.client.event.GuiOpenEvent; 17 | import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; 18 | import net.minecraftforge.fml.common.gameevent.TickEvent; 19 | import net.minecraftforge.fml.common.network.FMLNetworkEvent.ClientDisconnectionFromServerEvent; 20 | 21 | @SuppressWarnings("unused") 22 | public class EventHandler { 23 | 24 | static Order verify = null; 25 | static String[] productVerify = new String[2]; 26 | 27 | @SubscribeEvent 28 | public void bazaarChatHandler(ClientChatReceivedEvent e) { 29 | 30 | if (!BazaarNotifier.activeBazaar) { 31 | return; 32 | } 33 | String message = StringUtils.stripControlCodes(e.message.getUnformattedText()); 34 | 35 | if (message.startsWith("[Bazaar] Claimed") || message.startsWith("[Bazaar] Bought") 36 | || message.startsWith("[Bazaar] Sold")) { 37 | BankCalculator.evaluate(message); 38 | } 39 | 40 | if (message.startsWith("Buy Order Setup!") || message.startsWith("Sell Offer Setup!") 41 | || message.startsWith("[Bazaar] Buy Order Setup!") || message.startsWith( 42 | "[Bazaar] Sell Offer Setup!")) { 43 | if (productVerify[0] != null && productVerify[1] != null && productVerify[0].equals( 44 | BazaarNotifier.bazaarConv.inverse().get(message.split("x ", 2)[1].split(" for ")[0])) 45 | && productVerify[1].equals(message.split("! ")[1].split(" for ")[0])) { 46 | BazaarNotifier.orders.add(verify); 47 | BankCalculator.evaluateCapHit(verify); 48 | verify = null; 49 | productVerify = new String[2]; 50 | } 51 | } else if (message.startsWith("[Bazaar] Your ") && message.endsWith(" was filled!")) { 52 | String item = message.split("x ", 2)[1].split(" was ")[0]; 53 | int amount = Integer.parseInt( 54 | message.split(" for ")[1].split("x ", 2)[0].replaceAll(",", "")); 55 | int orderToRemove = 0; 56 | boolean found = false; 57 | double edgePrice; 58 | if (message.startsWith("[Bazaar] Your Buy Order")) { 59 | edgePrice = Double.MIN_VALUE; 60 | for (int i = 0; i < BazaarNotifier.orders.size(); i++) { 61 | Order order = BazaarNotifier.orders.get(i); 62 | if (order.product.equalsIgnoreCase(item) && order.startAmount == amount 63 | && order.type.equals(Order.OrderType.BUY) && order.pricePerUnit > edgePrice) { 64 | edgePrice = order.pricePerUnit; 65 | orderToRemove = i; 66 | found = true; 67 | 68 | } 69 | } 70 | } else if (message.startsWith("[Bazaar] Your Sell Offer")) { 71 | edgePrice = Double.MAX_VALUE; 72 | for (int i = 0; i < BazaarNotifier.orders.size(); i++) { 73 | Order order = BazaarNotifier.orders.get(i); 74 | if (order.product.equalsIgnoreCase(item) && order.startAmount == amount 75 | && order.type.equals(Order.OrderType.SELL) && order.pricePerUnit < edgePrice) { 76 | 77 | edgePrice = order.pricePerUnit; 78 | orderToRemove = i; 79 | found = true; 80 | } 81 | } 82 | } 83 | if (found) { 84 | BazaarNotifier.orders.remove(orderToRemove); 85 | } else { 86 | System.err.println("There is some error in removing your order from the list!!!"); 87 | } 88 | } else if (message.startsWith("Cancelled!") || message.startsWith("[Bazaar] Cancelled!")) { 89 | double refund = 0; 90 | int refundAmount = 0; 91 | String itemRefunded = ""; 92 | if (message.endsWith("Buy Order!")) { 93 | refund = Double.parseDouble( 94 | message.split("Refunded ")[1].split(" coins")[0].replaceAll(",", "")); 95 | if (refund >= 10000) { 96 | refund = Math.round(refund); 97 | } 98 | } else if (message.endsWith("Sell Offer!")) { 99 | refundAmount = Integer.parseInt( 100 | message.split("Refunded ")[1].split("x ", 2)[0].replaceAll(",", "")); 101 | itemRefunded = message.split("x ", 2)[1].split(" from")[0]; 102 | 103 | } 104 | for (int i = 0; i < BazaarNotifier.orders.size(); i++) { 105 | Order order = BazaarNotifier.orders.get(i); 106 | if (message.endsWith("Buy Order!") && order.type.equals(Order.OrderType.BUY)) { 107 | double remaining = 108 | (refund >= 10000 ? Math.round(order.orderValue) : order.orderValue) - refund; 109 | if (remaining <= 1 && remaining >= 0) { 110 | BazaarNotifier.orders.remove(i); 111 | break; 112 | } 113 | } else if (message.endsWith("Sell Offer!") && order.type.equals(Order.OrderType.SELL)) { 114 | if (order.product.equalsIgnoreCase(itemRefunded) 115 | && order.getAmountRemaining() == refundAmount) { 116 | BazaarNotifier.orders.remove(i); 117 | break; 118 | } 119 | } 120 | } 121 | } else if (message.startsWith("Bazaar! Claimed ") || message.startsWith("[Bazaar] Claimed")) { 122 | ChestTickHandler.lastScreenDisplayName = ""; 123 | } 124 | } 125 | 126 | @SubscribeEvent 127 | public void menuOpenedEvent(GuiOpenEvent e) { 128 | if (e.gui instanceof GuiChest) { 129 | IInventory chest = ReflectionHelper.getLowerChestInventory((GuiChest) e.gui); 130 | if (chest != null && ((chest.hasCustomName() && ( 131 | StringUtils.stripControlCodes(chest.getDisplayName().getUnformattedText()) 132 | .startsWith("Bazaar") || StringUtils.stripControlCodes( 133 | chest.getDisplayName().getUnformattedText()) 134 | .equalsIgnoreCase("How much do you want to pay?") || StringUtils.stripControlCodes( 135 | chest.getDisplayName().getUnformattedText()) 136 | .matches("Confirm (Buy|Sell) (Order|Offer)")) || StringUtils.stripControlCodes( 137 | chest.getDisplayName().getUnformattedText()).contains("Bazaar")) 138 | || BazaarNotifier.forceRender)) { 139 | BazaarNotifier.inBazaar = true; 140 | } 141 | } else if (e.gui == null || e.gui instanceof GuiEditSign) { 142 | BazaarNotifier.inBazaar = false; 143 | } 144 | } 145 | 146 | @SubscribeEvent 147 | public void disconnectEvent(ClientDisconnectionFromServerEvent e) { 148 | BazaarNotifier.inBazaar = false; 149 | } 150 | 151 | @SubscribeEvent 152 | public void renderEvent(TickEvent e) { 153 | if (BazaarNotifier.guiToOpen.contains("settings")) { 154 | if (Platform.getGuiPlatform().getCurrentScreen() == null) { 155 | Minecraft.getMinecraft().displayGuiScreen(OneConfigGui.create()); 156 | BazaarNotifier.guiToOpen = "settings-mod"; 157 | } else if (BazaarNotifier.guiToOpen.equals("settings-mod") 158 | && Platform.getGuiPlatform().getCurrentScreen() instanceof OneConfigGui) { 159 | OneConfigGui.INSTANCE.openPage( 160 | new ModConfigPage(BazaarNotifier.config.mod.defaultPage, true), 161 | new cc.polyfrost.oneconfig.gui.animations.DummyAnimation(2128), false); 162 | BazaarNotifier.guiToOpen = ""; 163 | } 164 | } 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /src/main/java/dev/meyi/bn/handlers/MouseHandler.java: -------------------------------------------------------------------------------- 1 | package dev.meyi.bn.handlers; 2 | 3 | import dev.meyi.bn.BazaarNotifier; 4 | import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; 5 | import net.minecraftforge.fml.common.gameevent.TickEvent; 6 | import org.lwjgl.input.Mouse; 7 | 8 | 9 | public class MouseHandler { 10 | 11 | public static int mouseWheelMovement = 0; 12 | int tick = 0; 13 | boolean inPageFlip = false; 14 | 15 | 16 | @SubscribeEvent 17 | public void mouseActionCheck(TickEvent e) { 18 | if (e.phase == TickEvent.Phase.START) { 19 | if (BazaarNotifier.inBazaar) { 20 | if(BazaarNotifier.config.legacyMovement) { 21 | BazaarNotifier.modules.movementCheck(); 22 | } 23 | if (tick >= 8 && !inPageFlip) { // 2.5 times per second 24 | inPageFlip = true; 25 | BazaarNotifier.modules.pageFlipCheck(); 26 | if (BazaarNotifier.config.legacyMovement) { 27 | BazaarNotifier.modules.rescaleCheck(); 28 | } 29 | BazaarNotifier.modules.shiftSettingCheck(); 30 | mouseWheel(); 31 | 32 | tick = 0; 33 | inPageFlip = false; 34 | } 35 | tick++; 36 | } else { 37 | tick = 0; 38 | } 39 | } 40 | } 41 | 42 | 43 | public void mouseWheel() { 44 | mouseWheelMovement = (int) Math.round((double) Mouse.getDWheel() * -1 / 100); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/dev/meyi/bn/handlers/UpdateHandler.java: -------------------------------------------------------------------------------- 1 | package dev.meyi.bn.handlers; 2 | 3 | import com.google.gson.Gson; 4 | import com.google.gson.JsonObject; 5 | import dev.meyi.bn.BazaarNotifier; 6 | import java.io.BufferedReader; 7 | import java.io.IOException; 8 | import java.io.InputStreamReader; 9 | import java.util.concurrent.ScheduledThreadPoolExecutor; 10 | import java.util.concurrent.TimeUnit; 11 | import java.util.regex.Matcher; 12 | import java.util.regex.Pattern; 13 | import net.minecraft.client.Minecraft; 14 | import net.minecraft.event.ClickEvent; 15 | import net.minecraft.event.ClickEvent.Action; 16 | import net.minecraft.util.ChatComponentText; 17 | import net.minecraft.util.EnumChatFormatting; 18 | import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; 19 | import net.minecraftforge.fml.common.network.FMLNetworkEvent; 20 | import org.apache.commons.io.IOUtils; 21 | import org.apache.http.client.methods.HttpGet; 22 | import org.apache.http.impl.client.HttpClientBuilder; 23 | 24 | public class UpdateHandler { 25 | 26 | boolean firstJoin = true; 27 | 28 | @SubscribeEvent 29 | public void onPlayerJoinEvent(FMLNetworkEvent.ClientConnectedToServerEvent event) { 30 | if (firstJoin) { 31 | firstJoin = false; 32 | new ScheduledThreadPoolExecutor(1).schedule(() -> { 33 | Gson gson = new Gson(); 34 | try { 35 | String[] currentTag; 36 | String[] latestTag; 37 | int currentBeta = 0; 38 | int latestBeta = 0; 39 | boolean beta = false; 40 | Pattern p = Pattern 41 | .compile("\"?([0-9]+\\.[0-9]+\\.[0-9]+)-beta([0-9]+)\"?$", Pattern.MULTILINE); 42 | Matcher m = p.matcher(BazaarNotifier.VERSION); 43 | if (m.find()) { 44 | beta = true; 45 | String version = m.group(1); 46 | currentTag = version.split("\\."); 47 | currentBeta = Integer.parseInt(m.group(2)); 48 | 49 | // Using the same pattern to check can be risky if there is another version in the build.gradle that also matches, but so far that isn't a problem. 50 | 51 | String buildGradle; 52 | try { 53 | buildGradle = IOUtils.toString(new BufferedReader(new InputStreamReader( 54 | HttpClientBuilder.create().build().execute(new HttpGet( 55 | "https://raw.githubusercontent.com/symt/BazaarNotifier/beta/build.gradle.kts")) 56 | .getEntity().getContent()))); 57 | } catch (IOException e) { 58 | Minecraft.getMinecraft().thePlayer.addChatMessage( 59 | new ChatComponentText(BazaarNotifier.prefix + EnumChatFormatting.RED 60 | + "There was an error reading the version. Make sure you aren't on an old beta version. /bn discord")); 61 | e.printStackTrace(); 62 | return; 63 | } 64 | Matcher m2 = p.matcher(buildGradle); 65 | if (m2.find()) { 66 | latestTag = m2.group(1).split("\\."); 67 | latestBeta = Integer.parseInt(m2.group(2)); 68 | } else { 69 | Minecraft.getMinecraft().thePlayer.addChatMessage( 70 | new ChatComponentText(BazaarNotifier.prefix + EnumChatFormatting.RED 71 | + "There was an error reading the beta version. No version matched in the build.gradle file.")); 72 | return; 73 | } 74 | } else { 75 | JsonObject json = gson 76 | .fromJson(IOUtils.toString(new BufferedReader(new InputStreamReader( 77 | HttpClientBuilder.create().build().execute(new HttpGet( 78 | "https://api.github.com/repos/symt/BazaarNotifier/releases/latest")) 79 | .getEntity().getContent()))), 80 | JsonObject.class).getAsJsonObject(); 81 | latestTag = json.get("tag_name").getAsString().split("\\."); 82 | currentTag = BazaarNotifier.VERSION.split("\\."); 83 | } 84 | 85 | // Assuming the user is on the correct beta version, this will pass without any chat messages. 86 | 87 | if (latestTag.length == 3 && currentTag.length == 3) { 88 | for (int i = 0; i < latestTag.length; i++) { 89 | int latestCheck = Integer.parseInt(latestTag[i]); 90 | int currentCheck = Integer.parseInt(currentTag[i]); 91 | 92 | if (latestCheck != currentCheck) { 93 | if (latestCheck < currentCheck) { 94 | Minecraft.getMinecraft().thePlayer.addChatMessage( 95 | new ChatComponentText(BazaarNotifier.prefix + EnumChatFormatting.RED 96 | + "This version hasn't been released yet. Please report any bugs that you come across.")); 97 | } else { 98 | ChatComponentText updateLink = new ChatComponentText( 99 | EnumChatFormatting.DARK_RED + "" + EnumChatFormatting.BOLD 100 | + "[UPDATE LINK]"); 101 | updateLink 102 | .setChatStyle(updateLink.getChatStyle().setChatClickEvent(new ClickEvent( 103 | Action.OPEN_URL, 104 | "https://github.com/symt/BazaarNotifier/releases/latest"))); 105 | Minecraft.getMinecraft().thePlayer.addChatMessage(new ChatComponentText( 106 | BazaarNotifier.prefix + EnumChatFormatting.RED 107 | + "The mod version that you're on is outdated. Please update for the best profits: ") 108 | .appendSibling(updateLink)); 109 | } 110 | beta = false; // Versions don't match, so betas don't matter 111 | break; 112 | } 113 | } 114 | } 115 | 116 | if (beta) { 117 | if (currentBeta > latestBeta) { 118 | Minecraft.getMinecraft().thePlayer.addChatMessage( 119 | new ChatComponentText(BazaarNotifier.prefix + EnumChatFormatting.RED 120 | + "This beta version hasn't been released yet. Please report any bugs that you come across.")); 121 | } else if (currentBeta < latestBeta) { 122 | Minecraft.getMinecraft().thePlayer.addChatMessage( 123 | new ChatComponentText(BazaarNotifier.prefix + EnumChatFormatting.RED 124 | + "You are on an outdated beta version. Please update via the discord server. Run /bn discord for the link")); 125 | } else { 126 | Minecraft.getMinecraft().thePlayer.addChatMessage( 127 | new ChatComponentText(BazaarNotifier.prefix + EnumChatFormatting.GREEN 128 | + "You are on a beta version. Please report any bugs you come across in the discord server.")); 129 | } 130 | } 131 | } catch (IOException e) { 132 | e.printStackTrace(); 133 | } 134 | }, 3, TimeUnit.SECONDS); 135 | } 136 | } 137 | } -------------------------------------------------------------------------------- /src/main/java/dev/meyi/bn/json/Exchange.java: -------------------------------------------------------------------------------- 1 | package dev.meyi.bn.json; 2 | 3 | import dev.meyi.bn.BazaarNotifier; 4 | import dev.meyi.bn.json.Order.OrderType; 5 | import dev.meyi.bn.modules.calc.CraftingCalculator; 6 | import java.util.Map; 7 | 8 | public class Exchange { 9 | 10 | private final String productId; 11 | private final double pricePerUnit; 12 | private int amount; 13 | private final OrderType type; 14 | 15 | private final Map craftingResources; 16 | 17 | public Exchange(OrderType type, String productId, double pricePerUnit, int amount) { 18 | this.type = type; 19 | this.productId = productId; 20 | this.pricePerUnit = pricePerUnit; 21 | this.amount = amount; 22 | 23 | if (canCraft()) { 24 | craftingResources = CraftingCalculator.getMaterialsMap(productId); 25 | } else { 26 | craftingResources = null; 27 | } 28 | } 29 | 30 | public String getProductId() { 31 | return productId; 32 | } 33 | 34 | public Map getCraftingResources() { 35 | return craftingResources; 36 | } 37 | 38 | public double getPricePerUnit() { 39 | return pricePerUnit; 40 | } 41 | 42 | public int getAmount() { 43 | return amount; 44 | } 45 | 46 | public void removeAmount(int remove) { 47 | this.amount -= remove; 48 | } 49 | 50 | public void addAmount(int add) { 51 | this.amount += add; 52 | } 53 | 54 | public OrderType getType() { 55 | return type; 56 | } 57 | 58 | public boolean matchesOrder(Exchange exchange) { 59 | return this.type != exchange.type && this.productId.equals(exchange.productId); 60 | } 61 | 62 | public boolean canCraft() { 63 | return type == OrderType.SELL && BazaarNotifier.enchantCraftingList.getAsJsonObject("other") 64 | .has(productId); 65 | } 66 | 67 | @Override 68 | public boolean equals(Object obj) { 69 | return (obj instanceof Exchange) && this.pricePerUnit == ((Exchange) obj).pricePerUnit 70 | && this.productId.equals(((Exchange) obj).productId); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/main/java/dev/meyi/bn/json/Order.java: -------------------------------------------------------------------------------- 1 | package dev.meyi.bn.json; 2 | 3 | 4 | import dev.meyi.bn.BazaarNotifier; 5 | import dev.meyi.bn.json.resp.BazaarItem; 6 | import dev.meyi.bn.utilities.RenderUtils; 7 | 8 | public class Order { 9 | 10 | public String product; 11 | public int startAmount; 12 | public double pricePerUnit; 13 | public String priceString; 14 | public OrderStatus orderStatus = OrderStatus.SEARCHING; 15 | public double orderValue; 16 | public OrderType type; 17 | public long creationTime; 18 | private int amountRemaining; 19 | 20 | public Order(String product, int startAmount, double pricePerUnit, String priceString, 21 | OrderType type) { 22 | this.product = product; 23 | this.startAmount = startAmount; 24 | amountRemaining = startAmount; 25 | this.pricePerUnit = pricePerUnit; 26 | this.priceString = priceString; 27 | this.type = type; 28 | orderValue = startAmount * pricePerUnit; 29 | creationTime = System.currentTimeMillis(); 30 | } 31 | 32 | public Order(String product, OrderType type, double pricePerUnit, int startAmount) { 33 | this.product = product; 34 | this.type = type; 35 | this.pricePerUnit = pricePerUnit; 36 | this.startAmount = startAmount; 37 | } 38 | 39 | public int getAmountRemaining() { 40 | return amountRemaining; 41 | } 42 | 43 | public void setAmountRemaining(int amountRemaining) { 44 | this.amountRemaining = amountRemaining; 45 | orderValue = amountRemaining * pricePerUnit; 46 | } 47 | 48 | public boolean matches(Order other) { 49 | return other.type == this.type && other.product.equals(this.product) 50 | && other.startAmount == this.startAmount && 51 | other.pricePerUnit == this.pricePerUnit; 52 | } 53 | 54 | public String getProductId() { 55 | return BazaarNotifier.bazaarConv.inverse().get(product); 56 | } 57 | 58 | public void updateStatus() { 59 | OrderStatus newOrderStatus; 60 | if (!BazaarNotifier.activeBazaar) { 61 | return; 62 | } 63 | if (OrderType.BUY.equals(this.type)) { 64 | if (BazaarNotifier.bazaarDataRaw.products.get(getProductId()).sell_summary.isEmpty()) { 65 | orderStatus = OrderStatus.SEARCHING; 66 | return; 67 | } 68 | BazaarItem.BazaarSubItem bazaarSubItem = BazaarNotifier.bazaarDataRaw.products 69 | .get(getProductId()).sell_summary.get(0); 70 | if(creationTime > BazaarNotifier.bazaarDataRaw.lastUpdated){ 71 | newOrderStatus = OrderStatus.SEARCHING; 72 | } 73 | else if (this.pricePerUnit < bazaarSubItem.pricePerUnit) { 74 | newOrderStatus = OrderStatus.OUTDATED; 75 | } else if (this.pricePerUnit == bazaarSubItem.pricePerUnit 76 | && this.startAmount >= bazaarSubItem.amount 77 | && bazaarSubItem.orders == 1) { //&& this.amountRemaining <= bazaarSubItem.amount 78 | newOrderStatus = OrderStatus.BEST; 79 | } else if (this.pricePerUnit > bazaarSubItem.pricePerUnit) { 80 | newOrderStatus = OrderStatus.SEARCHING; 81 | } else if (this.pricePerUnit == bazaarSubItem.pricePerUnit && bazaarSubItem.orders == 1) { 82 | newOrderStatus = OrderStatus.SEARCHING; 83 | } else if (this.pricePerUnit == bazaarSubItem.pricePerUnit && bazaarSubItem.orders > 1 && 84 | BazaarNotifier.orders.stream().filter(order -> this.product.equals(order.product) 85 | && this.pricePerUnit == order.pricePerUnit).count() != bazaarSubItem.orders) { 86 | newOrderStatus = OrderStatus.MATCHED; 87 | } else { 88 | newOrderStatus = OrderStatus.BEST; 89 | } 90 | } else { 91 | if (BazaarNotifier.bazaarDataRaw.products.get(getProductId()).buy_summary.isEmpty()) { 92 | orderStatus = OrderStatus.SEARCHING; 93 | return; 94 | } 95 | BazaarItem.BazaarSubItem bazaarSubItem = BazaarNotifier.bazaarDataRaw.products 96 | .get(getProductId()).buy_summary.get(0); 97 | if(creationTime > BazaarNotifier.bazaarDataRaw.lastUpdated){ 98 | newOrderStatus = OrderStatus.SEARCHING; 99 | } 100 | else if (this.pricePerUnit > bazaarSubItem.pricePerUnit) { 101 | newOrderStatus = OrderStatus.OUTDATED; 102 | } else if (this.pricePerUnit == bazaarSubItem.pricePerUnit 103 | && this.startAmount >= bazaarSubItem.amount && bazaarSubItem.orders == 1) { 104 | newOrderStatus = OrderStatus.BEST; 105 | } else if (this.pricePerUnit < bazaarSubItem.pricePerUnit) { 106 | newOrderStatus = OrderStatus.SEARCHING; 107 | } else if (this.pricePerUnit == bazaarSubItem.pricePerUnit && bazaarSubItem.orders == 1) { 108 | newOrderStatus = OrderStatus.SEARCHING; 109 | } else if (this.pricePerUnit == bazaarSubItem.pricePerUnit && bazaarSubItem.orders > 1 && 110 | BazaarNotifier.orders.stream().filter(order -> this.product.equals(order.product) 111 | && this.pricePerUnit == order.pricePerUnit).count() != bazaarSubItem.orders) { 112 | newOrderStatus = OrderStatus.MATCHED; 113 | } else { 114 | newOrderStatus = OrderStatus.BEST; 115 | } 116 | } 117 | if (this.orderStatus != newOrderStatus) { 118 | if (OrderStatus.BEST.equals(newOrderStatus) && this.orderStatus != OrderStatus.SEARCHING) { 119 | RenderUtils.chatNotification(this, "REVIVED"); 120 | } else if (OrderStatus.MATCHED.equals(newOrderStatus)) { 121 | RenderUtils.chatNotification(this, "MATCHED"); 122 | } else if (OrderStatus.OUTDATED.equals(newOrderStatus)) { 123 | RenderUtils.chatNotification(this, "OUTDATED"); 124 | } 125 | this.orderStatus = newOrderStatus; 126 | } 127 | } 128 | 129 | public enum OrderStatus {BEST, MATCHED, OUTDATED, SEARCHING} 130 | 131 | public enum OrderType { 132 | BUY("Buy Order"), 133 | SELL("Sell Offer"); 134 | public final String longName; 135 | 136 | OrderType(String longName) { 137 | this.longName = longName; 138 | } 139 | } 140 | } -------------------------------------------------------------------------------- /src/main/java/dev/meyi/bn/json/resp/BazaarItem.java: -------------------------------------------------------------------------------- 1 | package dev.meyi.bn.json.resp; 2 | 3 | import java.util.List; 4 | 5 | public class BazaarItem { 6 | 7 | public String product_id; 8 | 9 | public List sell_summary; 10 | public List buy_summary; 11 | 12 | public BazaarItemSummary quick_status; 13 | 14 | public BazaarItem(String product_id, List sell_summary, 15 | List buy_summary, 16 | BazaarItemSummary quick_status) { 17 | this.product_id = product_id; 18 | this.sell_summary = sell_summary; 19 | this.buy_summary = buy_summary; 20 | this.quick_status = quick_status; 21 | } 22 | 23 | public static class BazaarSubItem { 24 | 25 | public int amount; 26 | public double pricePerUnit; 27 | public int orders; 28 | 29 | public BazaarSubItem(int amount, double pricePerUnit, int orders) { 30 | this.amount = amount; 31 | this.pricePerUnit = pricePerUnit; 32 | this.orders = orders; 33 | } 34 | 35 | public double getPriceWithTax() { 36 | return pricePerUnit * 0.9875d; 37 | } 38 | } 39 | 40 | public static class BazaarItemSummary { 41 | 42 | public String productId; 43 | public double sellPrice; 44 | public long sellVolume; 45 | public long sellMovingWeek; 46 | public long sellOrders; 47 | public double buyPrice; 48 | public long buyVolume; 49 | public long buyMovingWeek; 50 | public long buyOrders; 51 | 52 | public BazaarItemSummary(String productId, double sellPrice, long sellVolume, 53 | long sellMovingWeek, long sellOrders, 54 | double buyPrice, long buyVolume, long buyMovingWeek, long buyOrders) { 55 | this.productId = productId; 56 | this.sellPrice = sellPrice; 57 | this.sellVolume = sellVolume; 58 | this.sellMovingWeek = sellMovingWeek; 59 | this.sellOrders = sellOrders; 60 | this.buyPrice = buyPrice; 61 | this.buyVolume = buyVolume; 62 | this.buyMovingWeek = buyMovingWeek; 63 | this.buyOrders = buyOrders; 64 | } 65 | 66 | } 67 | } 68 | 69 | 70 | -------------------------------------------------------------------------------- /src/main/java/dev/meyi/bn/json/resp/BazaarResponse.java: -------------------------------------------------------------------------------- 1 | package dev.meyi.bn.json.resp; 2 | 3 | import java.util.Map; 4 | 5 | public class BazaarResponse { 6 | 7 | public boolean success; 8 | public long lastUpdated; 9 | 10 | public Map products; 11 | 12 | public BazaarResponse(boolean success, long lastUpdated, Map products) { 13 | this.success = success; 14 | this.lastUpdated = lastUpdated; 15 | this.products = products; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/dev/meyi/bn/modules/Module.java: -------------------------------------------------------------------------------- 1 | package dev.meyi.bn.modules; 2 | 3 | import cc.polyfrost.oneconfig.config.annotations.Switch; 4 | import cc.polyfrost.oneconfig.gui.OneConfigGui; 5 | import cc.polyfrost.oneconfig.hud.Hud; 6 | import cc.polyfrost.oneconfig.platform.GuiPlatform; 7 | import cc.polyfrost.oneconfig.platform.Platform; 8 | import dev.meyi.bn.BazaarNotifier; 9 | import net.minecraft.client.Minecraft; 10 | import net.minecraft.client.gui.Gui; 11 | import net.minecraft.client.gui.ScaledResolution; 12 | import org.apache.commons.lang3.StringUtils; 13 | import org.lwjgl.input.Mouse; 14 | import org.lwjgl.opengl.Display; 15 | 16 | 17 | public abstract class Module extends Hud { 18 | protected transient int shift = 0; 19 | protected transient boolean moving = false; 20 | protected transient boolean needsToMove = false; 21 | protected transient int mouseWheelShift = 0; 22 | protected transient int padding = 3; 23 | private transient int lastMouseX, lastMouseY; 24 | 25 | protected transient String longestString = ""; 26 | 27 | 28 | 29 | @Switch(name="Show Module Outside of Bazaar", 30 | category = "General" 31 | ) 32 | protected boolean showEverywhere = false; 33 | 34 | public Module() { 35 | super(true, 0, 0, 0, 1); 36 | } 37 | 38 | @Override 39 | protected boolean shouldShow() { 40 | if(enabled) { 41 | GuiPlatform guiPlatform = Platform.getGuiPlatform(); 42 | if(BazaarNotifier.inBazaar){ 43 | //Drawing at this case is handled by the ChestTickHandler to keep it above the gray gui background; 44 | return false; 45 | } 46 | if (showInGuis && (guiPlatform.getCurrentScreen() instanceof OneConfigGui)) return false; 47 | if (showInChat && guiPlatform.isInChat()) return true; 48 | if (showInDebug && guiPlatform.isInDebug()) return true; 49 | if (showInGuis && guiPlatform.getCurrentScreen() != null) return true; 50 | return showEverywhere; 51 | }else { 52 | return false; 53 | } 54 | } 55 | public abstract void draw(); 56 | 57 | protected abstract void reset(); 58 | 59 | public abstract String name(); 60 | 61 | protected abstract boolean shouldDrawBounds(); 62 | 63 | protected abstract int getMaxShift(); 64 | 65 | public void handleMovement() { 66 | if (moving) { 67 | position.setPosition(position.getX() + getMouseCoordinateX() - lastMouseX, position.getY() + getMouseCoordinateY() - lastMouseY); 68 | } 69 | moving = true; 70 | lastMouseX = getMouseCoordinateX(); 71 | lastMouseY = getMouseCoordinateY(); 72 | } 73 | 74 | public void drawBounds() { 75 | if (shouldDrawBounds()) { 76 | Gui.drawRect((int)position.getX(), (int)position.getY(), 77 | (int)position.getRightX(), (int)position.getBottomY(), 0x66000000); 78 | } 79 | } 80 | 81 | public boolean inMovementBox() { 82 | return (getMouseCoordinateX() >= position.getX() 83 | && getMouseCoordinateY() >= position.getY() 84 | && getMouseCoordinateX() <= position.getRightX() 85 | && getMouseCoordinateY() <= position.getBottomY()); 86 | } 87 | 88 | 89 | protected int getMouseCoordinateX() { 90 | return Mouse.getX() / new ScaledResolution(Minecraft.getMinecraft()).getScaleFactor(); 91 | } 92 | 93 | protected int getMouseCoordinateY() { 94 | return (Display.getHeight() - Mouse.getY()) / new ScaledResolution(Minecraft.getMinecraft()) 95 | .getScaleFactor(); 96 | } 97 | 98 | public String getReadableName() { 99 | String name = StringUtils.lowerCase(name()); 100 | return StringUtils.capitalize(name) + " Module"; 101 | } 102 | 103 | public void setActive(boolean active) { 104 | enabled = active; 105 | } 106 | 107 | public float getModuleWidth(){ 108 | return this.getWidth(scale, false); 109 | } 110 | 111 | public float getModuleHeight(){ 112 | return this.getHeight(scale, false); } 113 | } 114 | -------------------------------------------------------------------------------- /src/main/java/dev/meyi/bn/modules/ModuleList.java: -------------------------------------------------------------------------------- 1 | package dev.meyi.bn.modules; 2 | 3 | import dev.meyi.bn.BazaarNotifier; 4 | import dev.meyi.bn.handlers.MouseHandler; 5 | import java.util.ArrayList; 6 | import org.lwjgl.input.Keyboard; 7 | import org.lwjgl.input.Mouse; 8 | 9 | public class ModuleList extends ArrayList { 10 | 11 | private Module movingModule = null; 12 | 13 | public ModuleList() { 14 | add(BazaarNotifier.config.suggestionModule); 15 | add(BazaarNotifier.config.bankModule); 16 | add(BazaarNotifier.config.notificationModule); 17 | add(BazaarNotifier.config.craftingModule); 18 | } 19 | 20 | public void rescaleCheck() { 21 | for (Module m : this) { 22 | if (m.inMovementBox() && Keyboard.isKeyDown(29) && !Keyboard.isKeyDown(42)) { 23 | float newScale = m.getScale() + (float) MouseHandler.mouseWheelMovement / 20; 24 | m.setScale(Math.max(newScale, 0.1f),false); 25 | } 26 | } 27 | } 28 | 29 | public void shiftSettingCheck() { 30 | for (Module m : this) { 31 | if (MouseHandler.mouseWheelMovement != 0 && Keyboard.isKeyDown(42) && !Keyboard 32 | .isKeyDown(29)) { 33 | m.mouseWheelShift -= MouseHandler.mouseWheelMovement; 34 | if (m.mouseWheelShift < 1) { 35 | m.mouseWheelShift = 1; 36 | } 37 | } 38 | } 39 | } 40 | 41 | public void movementCheck() { 42 | if (Mouse.isButtonDown(0)) { 43 | if (movingModule == null) { 44 | for (Module m : this) { 45 | if (m.inMovementBox()) { 46 | m.needsToMove = true; 47 | } 48 | } 49 | for (Module m : this) { 50 | if (movingModule != null) { 51 | m.needsToMove = false; 52 | } else if (m.needsToMove) { 53 | movingModule = m; 54 | } 55 | } 56 | } 57 | if (movingModule != null) { 58 | movingModule.handleMovement(); 59 | } 60 | } else { 61 | if (movingModule != null) { 62 | movingModule.needsToMove = false; 63 | movingModule.moving = false; 64 | } 65 | movingModule = null; 66 | } 67 | } 68 | 69 | public void pageFlipCheck() { 70 | if (!Keyboard.isKeyDown(29) && !Keyboard.isKeyDown(42)) { 71 | if (MouseHandler.mouseWheelMovement != 0) { 72 | for (Module m : this) { 73 | if (m.inMovementBox() && m.getMaxShift() > 0) { 74 | m.shift += MouseHandler.mouseWheelMovement; 75 | if (m.shift > m.getMaxShift()) { 76 | m.shift = m.getMaxShift(); 77 | } else if (m.shift < 0) { 78 | m.shift = 0; 79 | } 80 | 81 | break; 82 | } 83 | } 84 | } 85 | } 86 | } 87 | public void drawAllGui(){ 88 | if (BazaarNotifier.config.enabled && BazaarNotifier.activeBazaar) { 89 | for (Module m : this) { 90 | if (m.isEnabled() && BazaarNotifier.inBazaar) { 91 | m.position.setSize(m.getModuleWidth(), m.getModuleHeight()); 92 | m.draw(); 93 | } 94 | } 95 | } 96 | } 97 | 98 | 99 | public void resetAll() { 100 | for (Module m : this) { 101 | m.reset(); 102 | } 103 | } 104 | 105 | public void resetScale() { 106 | for (Module m : this) { 107 | m.setScale(1, false); 108 | } 109 | } 110 | 111 | 112 | } 113 | -------------------------------------------------------------------------------- /src/main/java/dev/meyi/bn/modules/ModuleName.java: -------------------------------------------------------------------------------- 1 | package dev.meyi.bn.modules; 2 | 3 | import dev.meyi.bn.modules.module.BankModule; 4 | import dev.meyi.bn.modules.module.CraftingModule; 5 | import dev.meyi.bn.modules.module.NotificationModule; 6 | import dev.meyi.bn.modules.module.SuggestionModule; 7 | 8 | public enum ModuleName { 9 | SUGGESTION, BANK, NOTIFICATION, CRAFTING; 10 | 11 | public Module returnDefaultModule() { 12 | switch (this) { 13 | case SUGGESTION: 14 | return new SuggestionModule(); 15 | case BANK: 16 | return new BankModule(); 17 | case NOTIFICATION: 18 | return new NotificationModule(); 19 | case CRAFTING: 20 | return new CraftingModule(); 21 | } 22 | return null; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/dev/meyi/bn/modules/calc/BankCalculator.java: -------------------------------------------------------------------------------- 1 | package dev.meyi.bn.modules.calc; 2 | 3 | import dev.meyi.bn.BazaarNotifier; 4 | import dev.meyi.bn.json.Exchange; 5 | import dev.meyi.bn.json.Order; 6 | import dev.meyi.bn.json.Order.OrderType; 7 | import dev.meyi.bn.utilities.Utils; 8 | import java.util.ArrayList; 9 | import java.util.HashMap; 10 | import java.util.List; 11 | import java.util.Map; 12 | import java.util.Map.Entry; 13 | import java.util.regex.Matcher; 14 | import java.util.regex.Pattern; 15 | 16 | 17 | public class BankCalculator { 18 | 19 | private static final List orderHistory = new ArrayList<>(); 20 | private static final Pattern sellOffer = Pattern.compile( 21 | "\\[Bazaar] Claimed .* coins from selling (.*)x (.*) at (.*) each!"); 22 | private static final Pattern buyOrder = Pattern.compile( 23 | "\\[Bazaar] Claimed (.*)x (.*) worth .* coins bought for (.*) each!"); 24 | private static final Pattern instantSell = Pattern.compile( 25 | "\\[Bazaar] Sold (.*)x (.*) for (.*) coins!"); 26 | private static final Pattern instantBuy = Pattern.compile( 27 | "\\[Bazaar] Bought (.*)x (.*) for (.*) coins!"); 28 | private static double rawDifference = 0; 29 | public static double getRawDifference() { 30 | return rawDifference; 31 | } 32 | 33 | public static void calculateBazaarProfit() { 34 | for (int i = orderHistory.size() - 1; i >= 0; i--) { 35 | Exchange sell = orderHistory.get(i); 36 | if (sell.getAmount() != 0 && sell.getType() == OrderType.SELL) { 37 | for (int j = orderHistory.size() - 1; j >= 0; j--) { 38 | Exchange buy = orderHistory.get(j); 39 | if (buy.getAmount() != 0 && sell.matchesOrder(buy)) { 40 | if (buy.getAmount() >= sell.getAmount()) { 41 | BazaarNotifier.config.bankModule.bazaarProfit += 42 | sell.getAmount() * (sell.getPricePerUnit() * .99 - buy.getPricePerUnit()); 43 | buy.removeAmount(sell.getAmount()); 44 | sell.removeAmount(sell.getAmount()); 45 | } else { 46 | BazaarNotifier.config.bankModule.bazaarProfit += 47 | buy.getAmount() * (sell.getPricePerUnit() * .99 - buy.getPricePerUnit()); 48 | sell.removeAmount(buy.getAmount()); 49 | buy.removeAmount(buy.getAmount()); 50 | } 51 | 52 | if (sell.getAmount() == 0) { 53 | break; 54 | } 55 | } 56 | } 57 | } 58 | } 59 | 60 | orderHistory.removeIf(v -> v.getAmount() == 0); 61 | 62 | craftingLoop: 63 | for (int i = orderHistory.size() - 1; i >= 0; i--) { 64 | if (orderHistory.get(i).getAmount() != 0 && orderHistory.get(i).canCraft()) { 65 | Map craftingResources = orderHistory.get(i).getCraftingResources(); 66 | Map> availableResources = new HashMap<>(); 67 | craftingResources.keySet().forEach(key -> availableResources.put(key, new ArrayList<>())); 68 | for (Exchange exchange : orderHistory) { 69 | if (exchange.getType() == OrderType.BUY && craftingResources.containsKey( 70 | exchange.getProductId())) { 71 | availableResources.get(exchange.getProductId()).add(exchange); 72 | } 73 | } 74 | 75 | int maxCrafting = orderHistory.get(i).getAmount(); 76 | 77 | for (Entry> entry : availableResources.entrySet()) { 78 | int amountAvailable = 0; 79 | for (Exchange e : entry.getValue()) { 80 | amountAvailable += e.getAmount(); 81 | } 82 | 83 | int craftCost = craftingResources.get(entry.getKey()); 84 | 85 | if (amountAvailable < craftCost) { 86 | break craftingLoop; 87 | } else { 88 | maxCrafting = Math.min(maxCrafting, amountAvailable / craftCost); 89 | } 90 | } 91 | 92 | double buyValue = 0; 93 | 94 | if (maxCrafting > 0) { 95 | for (String key : availableResources.keySet()) { 96 | int valueToRemove = maxCrafting * craftingResources.get(key); 97 | for (Exchange e : availableResources.get(key)) { 98 | if (e.getAmount() >= valueToRemove) { 99 | buyValue += valueToRemove * e.getPricePerUnit(); 100 | e.removeAmount(valueToRemove); 101 | valueToRemove = 0; 102 | } else { 103 | buyValue += e.getAmount() * e.getPricePerUnit(); 104 | valueToRemove -= e.getAmount(); 105 | e.removeAmount(e.getAmount()); 106 | } 107 | } 108 | } 109 | } 110 | orderHistory.get(i).removeAmount(maxCrafting); 111 | BazaarNotifier.config.bankModule.bazaarProfit += 112 | ((double) maxCrafting * orderHistory.get(i).getPricePerUnit()) * .99 - buyValue; 113 | } 114 | } 115 | 116 | orderHistory.removeIf(v -> v.getAmount() == 0); 117 | } 118 | 119 | public static void evaluate(String message) { 120 | String productId; 121 | double pricePerUnit; 122 | int amount; 123 | OrderType type; 124 | 125 | Matcher m; 126 | 127 | if ((m = sellOffer.matcher(message)).find()) { 128 | type = OrderType.SELL; 129 | amount = Integer.parseInt(m.group(1).replaceAll("[,.]", "")); 130 | pricePerUnit = Double.parseDouble(m.group(3).replaceAll(",", "")); 131 | } else if ((m = instantSell.matcher(message)).find()) { 132 | type = OrderType.SELL; 133 | amount = Integer.parseInt(m.group(1).replaceAll("[,.]", "")); 134 | double coins = Double.parseDouble(m.group(3).replaceAll(",", "")); 135 | pricePerUnit = coins / (double) amount; 136 | } else if ((m = buyOrder.matcher(message)).find()) { 137 | type = OrderType.BUY; 138 | amount = Integer.parseInt(m.group(1).replaceAll("[,.]", "")); 139 | pricePerUnit = Double.parseDouble(m.group(3).replaceAll(",", "")); 140 | } else if ((m = instantBuy.matcher(message)).find()) { 141 | type = OrderType.BUY; 142 | amount = Integer.parseInt(m.group(1).replaceAll("[,.]", "")); 143 | double coins = Double.parseDouble(m.group(3).replaceAll(",", "")); 144 | pricePerUnit = coins / (double) amount; 145 | } else { 146 | return; 147 | } 148 | 149 | if (BazaarNotifier.bazaarConv.containsValue(m.group(2))) { 150 | productId = BazaarNotifier.bazaarConv.inverse().get(m.group(2)); 151 | } else { 152 | productId = Utils.getItemIdFromName(m.group(2))[1]; 153 | } 154 | 155 | if (!productId.isEmpty()) { 156 | Exchange e = new Exchange(type, productId, pricePerUnit, amount); 157 | 158 | int index; 159 | if ((index = orderHistory.indexOf(e)) != -1) { 160 | orderHistory.get(index).addAmount(amount); 161 | } else { 162 | orderHistory.add(e); 163 | } 164 | 165 | rawDifference += (((e.getType() == OrderType.BUY) ? -1.0 : 0.99) * e.getPricePerUnit() * (double)e.getAmount()); 166 | // Regardless of type, because reverse flipping is possible 167 | // aka selling an item you already own and buying back cheaper 168 | calculateBazaarProfit(); 169 | } 170 | } 171 | 172 | public static void evaluateCapHit(Order order) { 173 | BazaarNotifier.config.bankModule.bazaarDailyAmount -= order.orderValue; 174 | } 175 | 176 | public static synchronized void reset() { 177 | BazaarNotifier.config.bankModule.bazaarProfit = 0; 178 | orderHistory.clear(); 179 | rawDifference = 0; 180 | } 181 | } -------------------------------------------------------------------------------- /src/main/java/dev/meyi/bn/modules/calc/CraftingCalculator.java: -------------------------------------------------------------------------------- 1 | package dev.meyi.bn.modules.calc; 2 | 3 | import com.google.gson.JsonArray; 4 | import com.google.gson.JsonElement; 5 | import dev.meyi.bn.BazaarNotifier; 6 | import dev.meyi.bn.modules.module.CraftingModule; 7 | import dev.meyi.bn.utilities.Utils; 8 | import java.io.IOException; 9 | import java.util.ArrayList; 10 | import java.util.Arrays; 11 | import java.util.HashMap; 12 | import java.util.List; 13 | import java.util.Map; 14 | 15 | 16 | public class CraftingCalculator { 17 | 18 | private static List unlockedRecipes = new ArrayList<>(); 19 | 20 | 21 | public static void getBestEnchantRecipes() { 22 | ArrayList list = new ArrayList<>(); 23 | if (BazaarNotifier.enchantCraftingList == null 24 | || BazaarNotifier.bazaarDataRaw.products.size() == 0) { 25 | return; 26 | } 27 | for (Map.Entry keys : BazaarNotifier.enchantCraftingList 28 | .getAsJsonObject("other").entrySet()) { 29 | 30 | String itemName = keys.getKey(); 31 | String collection = BazaarNotifier.enchantCraftingList.getAsJsonObject("other") 32 | .getAsJsonObject(itemName) 33 | .get("collection").getAsString(); 34 | 35 | if (BazaarNotifier.config.collectionCheck && !(unlockedRecipes.contains(collection) 36 | || collection.equalsIgnoreCase("NONE"))) { 37 | continue; 38 | } 39 | 40 | String[] itemCrafting = getEnchantCraft(itemName); 41 | 42 | if (!itemCrafting[6].equalsIgnoreCase("0")) { 43 | list.add(itemCrafting); 44 | } 45 | } 46 | CraftingModule.list = list; 47 | 48 | sort(); 49 | } 50 | 51 | 52 | public static void toggleCrafting() { 53 | BazaarNotifier.config.craftingModule.craftingSortingOption = 54 | (BazaarNotifier.config.craftingModule.craftingSortingOption + 1) % 3; 55 | } 56 | 57 | public static String[] getEnchantCraft(String itemName) { 58 | String[] values = new String[7]; 59 | Arrays.fill(values, "0"); 60 | 61 | if (BazaarNotifier.enchantCraftingList.getAsJsonObject("other").has(itemName)) { 62 | if (BazaarNotifier.bazaarDataRaw.products.size() != 0) { 63 | if (BazaarNotifier.bazaarDataRaw.products.get(itemName).buy_summary.size() == 0 64 | || BazaarNotifier.bazaarDataRaw.products.get(itemName).sell_summary.size() == 0) { 65 | return values; 66 | } 67 | 68 | // buy order / instant sell 69 | double itemSellPrice = BazaarNotifier.bazaarDataRaw.products.get(itemName).sell_summary 70 | .get(0).getPriceWithTax(); 71 | 72 | // instant buy / sell offer 73 | double itemBuyPrice = BazaarNotifier.bazaarDataRaw.products.get(itemName).buy_summary 74 | .get(0).getPriceWithTax(); 75 | double ingredientPrice = 0d; 76 | int ingredientCount; 77 | double materialCost = 0d; 78 | double ingredientPrice2 = 0d; 79 | int ingredientCount2; 80 | double materialCost2 = 0d; 81 | 82 | boolean missingMaterialOrder = false; 83 | boolean missingMaterialInstant = false; 84 | 85 | //Buy orders 86 | for (int h = 0; h < BazaarNotifier.enchantCraftingList.getAsJsonObject("other") 87 | .getAsJsonObject(itemName).getAsJsonArray("material").size(); h++) { 88 | if (h % 2 == 0) { 89 | if (BazaarNotifier.bazaarDataRaw.products.get( 90 | BazaarNotifier.enchantCraftingList.getAsJsonObject("other") 91 | .getAsJsonObject(itemName) 92 | .getAsJsonArray("material").get(h).getAsString()).sell_summary.size() == 0) { 93 | missingMaterialOrder = true; 94 | break; 95 | } 96 | ingredientPrice = BazaarNotifier.bazaarDataRaw.products.get( 97 | BazaarNotifier.enchantCraftingList.getAsJsonObject("other") 98 | .getAsJsonObject(itemName).getAsJsonArray("material").get(h).getAsString()) 99 | .sell_summary.get(0).pricePerUnit; 100 | } else { 101 | ingredientCount = BazaarNotifier.enchantCraftingList.getAsJsonObject("other") 102 | .getAsJsonObject(itemName).getAsJsonArray("material").get(h).getAsInt(); 103 | materialCost += (ingredientPrice * ingredientCount); 104 | } 105 | } 106 | 107 | //Instant buy 108 | for (int h = 0; h < BazaarNotifier.enchantCraftingList.getAsJsonObject("other") 109 | .getAsJsonObject(itemName).getAsJsonArray("material").size(); h++) { 110 | if (h % 2 == 0) { 111 | if (BazaarNotifier.bazaarDataRaw.products.get( 112 | BazaarNotifier.enchantCraftingList.getAsJsonObject("other") 113 | .getAsJsonObject(itemName) 114 | .getAsJsonArray("material").get(h).getAsString()).buy_summary.size() == 0) { 115 | missingMaterialInstant = true; 116 | break; 117 | } 118 | ingredientPrice2 = BazaarNotifier.bazaarDataRaw.products.get( 119 | BazaarNotifier.enchantCraftingList.getAsJsonObject("other") 120 | .getAsJsonObject(itemName).getAsJsonArray("material").get(h).getAsString()) 121 | .buy_summary.get(0).pricePerUnit; 122 | } else { 123 | ingredientCount2 = BazaarNotifier.enchantCraftingList.getAsJsonObject("other") 124 | .getAsJsonObject(itemName).getAsJsonArray("material").get(h).getAsInt(); 125 | materialCost2 += (ingredientPrice2 * ingredientCount2); 126 | } 127 | } 128 | 129 | if (!missingMaterialOrder) { 130 | double profitInstaSell = itemSellPrice - materialCost; 131 | double profitSellOffer = itemBuyPrice - materialCost; 132 | double profitPercentage = (itemSellPrice / materialCost - 1) * 100; 133 | values[0] = String.valueOf(profitInstaSell); 134 | values[1] = String.valueOf(profitSellOffer); 135 | values[2] = String.valueOf(profitPercentage); 136 | } 137 | 138 | if (!missingMaterialInstant) { 139 | double profitInstaSell2 = itemSellPrice - materialCost2; 140 | double profitSellOffer2 = itemBuyPrice - materialCost2; 141 | double profitPercentage2 = (itemSellPrice / materialCost2 - 1) * 100; 142 | values[3] = String.valueOf(profitInstaSell2); 143 | values[4] = String.valueOf(profitSellOffer2); 144 | values[5] = String.valueOf(profitPercentage2); 145 | } 146 | } 147 | values[6] = itemName; 148 | } 149 | 150 | return values; 151 | 152 | } 153 | 154 | public static Map getMaterialsMap(String productId) { 155 | JsonArray materialsArray = BazaarNotifier.enchantCraftingList.getAsJsonObject("other") 156 | .getAsJsonObject(productId).getAsJsonArray("material"); 157 | Map materials = new HashMap<>(); 158 | for (int i = 0; i < materialsArray.size(); i += 2) { 159 | materials.put(materialsArray.get(i).getAsString(), materialsArray.get(i + 1).getAsInt()); 160 | } 161 | return materials; 162 | } 163 | 164 | public static void getUnlockedRecipes() { 165 | try { 166 | List s = Utils.unlockedRecipes(); 167 | if (s != null) { 168 | unlockedRecipes = s; 169 | 170 | // Honestly, if this is empty, we should just assume something went wrong and disable the collection check. 171 | if (unlockedRecipes.size() == 0) { 172 | BazaarNotifier.config.collectionCheck = false; 173 | } 174 | } else { 175 | BazaarNotifier.config.collectionCheck = false; 176 | } 177 | } catch (IOException e) { 178 | e.printStackTrace(); 179 | } 180 | } 181 | 182 | public static void sort() { 183 | int i = BazaarNotifier.config.craftingModule.useBuyOrders ? 0 : 3; 184 | 185 | CraftingModule.list.sort((o1, o2) -> Double.compare( 186 | Double.parseDouble(o2[i + BazaarNotifier.config.craftingModule.craftingSortingOption]), 187 | Double.parseDouble(o1[i + BazaarNotifier.config.craftingModule.craftingSortingOption]) 188 | )); 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /src/main/java/dev/meyi/bn/modules/calc/SuggestionCalculator.java: -------------------------------------------------------------------------------- 1 | package dev.meyi.bn.modules.calc; 2 | 3 | 4 | import dev.meyi.bn.BazaarNotifier; 5 | import dev.meyi.bn.json.resp.BazaarItem; 6 | import dev.meyi.bn.modules.module.SuggestionModule; 7 | import java.util.Collections; 8 | import java.util.Comparator; 9 | import java.util.LinkedList; 10 | import java.util.List; 11 | import java.util.Map; 12 | 13 | 14 | public class SuggestionCalculator { 15 | 16 | public static void basic() { 17 | try { 18 | List list = new LinkedList<>(); 19 | for (Map.Entry entry : BazaarNotifier.bazaarDataRaw.products 20 | .entrySet()) { 21 | String key = entry.getKey(); 22 | BazaarItem product = BazaarNotifier.bazaarDataRaw.products.get(key); 23 | 24 | if (!BazaarNotifier.config.suggestionModule.suggestionShowEnchantments && 25 | key.startsWith("ENCHANTMENT")) { 26 | continue; 27 | } 28 | 29 | if (!BazaarNotifier.bazaarConv.containsKey(key)) { 30 | BazaarNotifier.bazaarConv.put(key, key); 31 | } 32 | String productId = BazaarNotifier.bazaarConv.get(key); 33 | 34 | list.add(new String[]{productId, Double.toString(calculateEP(product))}); 35 | } 36 | list.sort(Comparator.comparingDouble(o -> Double.parseDouble(o[1]))); 37 | Collections.reverse(list); 38 | SuggestionModule.list = list; 39 | 40 | } catch (Exception e) { 41 | e.printStackTrace(); 42 | } 43 | } 44 | 45 | public static double calculateEP(BazaarItem item) { 46 | double sellPrice = (item.buy_summary.size() > 0 && item.sell_summary.size() > 0) ? 47 | item.buy_summary.get(0).pricePerUnit : 0; 48 | double buyPrice = (item.sell_summary.size() > 0 && item.buy_summary.size() > 0) ? 49 | item.sell_summary.get(0).pricePerUnit : 0; 50 | long sellMovingWeek = item.quick_status.sellMovingWeek; 51 | long buyMovingWeek = item.quick_status.buyMovingWeek; 52 | return (buyMovingWeek + sellMovingWeek == 0) ? 0 : ((buyMovingWeek * sellMovingWeek) / 53 | (10080d * (buyMovingWeek + sellMovingWeek))) * (sellPrice * .99d - buyPrice); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/dev/meyi/bn/modules/module/BankModule.java: -------------------------------------------------------------------------------- 1 | package dev.meyi.bn.modules.module; 2 | 3 | import cc.polyfrost.oneconfig.config.annotations.Switch; 4 | import cc.polyfrost.oneconfig.config.migration.JsonName; 5 | import cc.polyfrost.oneconfig.libs.universal.UMatrixStack; 6 | import dev.meyi.bn.BazaarNotifier; 7 | import dev.meyi.bn.modules.Module; 8 | import dev.meyi.bn.modules.ModuleName; 9 | import dev.meyi.bn.modules.calc.BankCalculator; 10 | import dev.meyi.bn.utilities.ColoredText; 11 | import dev.meyi.bn.utilities.RenderUtils; 12 | import dev.meyi.bn.utilities.Defaults; 13 | import java.awt.Color; 14 | import java.util.ArrayList; 15 | 16 | import net.minecraft.client.Minecraft; 17 | import org.lwjgl.opengl.GL11; 18 | 19 | public class BankModule extends Module { 20 | @JsonName("bazaarProfit") 21 | public double bazaarProfit = 0; 22 | 23 | @JsonName("bazaarDailyAmount") 24 | public double bazaarDailyAmount = 1E10; 25 | 26 | public BankModule() { 27 | super(); 28 | } 29 | 30 | public transient static final ModuleName type = ModuleName.BANK; 31 | transient int lines = 2; 32 | 33 | @JsonName("bankRawDifference") 34 | @Switch(name = "Raw Difference", 35 | category = "Bank Module", 36 | description = "Show profit including current orders" 37 | ) 38 | public boolean bankRawDifference = Defaults.BANK_RAW_DIFFERENCE; 39 | 40 | @Override 41 | protected void draw(UMatrixStack matrices, float x, float y, float scale, boolean example) { 42 | draw(); 43 | } 44 | 45 | @Override 46 | protected float getWidth(float scale, boolean example) { 47 | return RenderUtils.getStringWidth(longestString)* scale + 2 * padding * scale; 48 | } 49 | 50 | @Override 51 | protected float getHeight(float scale, boolean example) { 52 | return (((Minecraft.getMinecraft().fontRendererObj.FONT_HEIGHT * lines) + lines ) * scale - 2) + 2 * padding * scale; 53 | } 54 | 55 | 56 | @Override 57 | public void draw() { 58 | GL11.glTranslated(0, 0, 1); 59 | drawBounds(); 60 | ArrayList> items = new ArrayList<>(); 61 | 62 | ArrayList header = new ArrayList<>(); 63 | header.add(new ColoredText("Bank Module", BazaarNotifier.config.infoColor.toJavaColor())); 64 | items.add(header); 65 | 66 | ArrayList bazaarProfitMessage = new ArrayList<>(); 67 | bazaarProfitMessage.add(new ColoredText("Bazaar Profit: ", BazaarNotifier.config.itemColor.toJavaColor())); 68 | bazaarProfitMessage.add(new ColoredText(BazaarNotifier.df.format(bazaarProfit), Color.ORANGE)); 69 | items.add(bazaarProfitMessage); 70 | 71 | 72 | if (BazaarNotifier.config.bankModule.bankRawDifference) { 73 | ArrayList bazaarDifferenceMessage = new ArrayList<>(); 74 | bazaarDifferenceMessage.add(new ColoredText("Bazaar Difference: ", BazaarNotifier.config.itemColor.toJavaColor())); 75 | bazaarDifferenceMessage.add(new ColoredText(BazaarNotifier.df.format(BankCalculator.getRawDifference()), Color.ORANGE)); 76 | items.add(bazaarDifferenceMessage); 77 | } 78 | 79 | if (BazaarNotifier.config.bankModule.bazaarDailyAmount <= 1E9) { 80 | ArrayList bazaarDifferenceMessage = new ArrayList<>(); 81 | bazaarDifferenceMessage.add(new ColoredText("Daily Limit: ", BazaarNotifier.config.itemColor.toJavaColor())); 82 | if (BazaarNotifier.config.bankModule.bazaarDailyAmount > 0) { 83 | bazaarDifferenceMessage.add(new ColoredText( 84 | BazaarNotifier.df.format(Math.max(BazaarNotifier.config.bankModule.bazaarDailyAmount, 0)), 85 | Color.ORANGE)); 86 | } else { 87 | bazaarDifferenceMessage.add(new ColoredText("NONE", Color.RED)); 88 | } 89 | items.add(bazaarDifferenceMessage); 90 | } 91 | 92 | lines = 2 + (BazaarNotifier.config.bankModule.bankRawDifference ? 1 : 0) 93 | + (BazaarNotifier.config.bankModule.bazaarDailyAmount <= 1E9 ? 1 : 0); 94 | 95 | 96 | longestString = RenderUtils.getLongestString(items); 97 | RenderUtils.drawColorfulParagraph(items, (int)position.getX() + padding, (int)position.getY() + padding, scale); 98 | GL11.glTranslated(0, 0, -1); 99 | } 100 | 101 | @Override 102 | protected void reset() { 103 | position.setPosition(Defaults.BANK_MODULE_X, Defaults.BANK_MODULE_Y); 104 | setScale(1, false); 105 | 106 | BankCalculator.reset(); 107 | enabled = true; 108 | } 109 | 110 | @Override 111 | public String name() { 112 | return ModuleName.BANK.name(); 113 | } 114 | 115 | @Override 116 | protected boolean shouldDrawBounds() { 117 | return true; 118 | } 119 | 120 | @Override 121 | protected int getMaxShift() { 122 | return 0; 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /src/main/java/dev/meyi/bn/modules/module/CraftingModule.java: -------------------------------------------------------------------------------- 1 | package dev.meyi.bn.modules.module; 2 | 3 | import cc.polyfrost.oneconfig.config.annotations.Checkbox; 4 | import cc.polyfrost.oneconfig.config.annotations.Dropdown; 5 | import cc.polyfrost.oneconfig.config.annotations.DualOption; 6 | import cc.polyfrost.oneconfig.config.annotations.Slider; 7 | import cc.polyfrost.oneconfig.config.migration.JsonName; 8 | import cc.polyfrost.oneconfig.libs.universal.UMatrixStack; 9 | import dev.meyi.bn.BazaarNotifier; 10 | import dev.meyi.bn.modules.Module; 11 | import dev.meyi.bn.modules.ModuleName; 12 | import dev.meyi.bn.modules.calc.CraftingCalculator; 13 | import dev.meyi.bn.utilities.ColoredText; 14 | import dev.meyi.bn.utilities.Defaults; 15 | import dev.meyi.bn.utilities.RenderUtils; 16 | import java.awt.Color; 17 | import java.util.ArrayList; 18 | import net.minecraft.client.Minecraft; 19 | import net.minecraft.client.gui.Gui; 20 | import org.lwjgl.input.Mouse; 21 | import org.lwjgl.opengl.GL11; 22 | 23 | 24 | public class CraftingModule extends Module { 25 | 26 | public transient static final ModuleName type = ModuleName.CRAFTING; 27 | public transient static ArrayList list = new ArrayList<>(); 28 | private transient static boolean mouseButtonDown; 29 | @JsonName("craftingListLength") 30 | @Slider(name = "Crafting List Entries", 31 | category = "Crafting Module", 32 | description = "The amount of entries in the Crafting Module list", 33 | min = 1, max = 25, step = 1 34 | ) 35 | public int craftingListLength = Defaults.CRAFTING_LIST_LENGTH; 36 | @JsonName("craftingSortingOption") 37 | @Dropdown(name = "Crafting Sorting Option", 38 | options = {"Instant Sell", "Sell Offer", "1m instant"}, 39 | category = "Crafting Module", 40 | description = "After which condition should the crafting module be sorted" 41 | ) 42 | public int craftingSortingOption = Defaults.CRAFTING_SORTING_OPTION; 43 | @JsonName("useBuyOrders") 44 | @DualOption(name = "Use Buy Orders", 45 | left = "Instant Buy", 46 | right = "Buy Orders", 47 | description = "How you want to buy the crafting materials", 48 | category = "Crafting Module" 49 | ) 50 | public boolean useBuyOrders = Defaults.USE_BUY_ORDERS; 51 | @Checkbox(name = "Show Instant Sell Profit", 52 | category = "Crafting Module", 53 | description = "Shows the instant sell profit tab" 54 | ) 55 | public boolean showInstantSellProfit = Defaults.INSTANT_SELL_PROFIT; 56 | @Checkbox(name = "Show Sell Offer Profit", 57 | category = "Crafting Module", 58 | description = "Shows the sell offer profit tab" 59 | ) 60 | public boolean showSellOfferProfit = Defaults.SELL_OFFER_PROFIT; 61 | @Checkbox(name = "Show Profit Percentage", 62 | category = "Crafting Module", 63 | description = "Shows the profit percentage tab" 64 | ) 65 | public boolean showProfitPerMil = Defaults.PROFIT_PER_MIL; 66 | transient int lastHovered = 0; 67 | 68 | 69 | public CraftingModule() { 70 | super(); 71 | } 72 | 73 | @Override 74 | protected void draw(UMatrixStack matrices, float x, float y, float scale, boolean example) { 75 | draw(); 76 | } 77 | 78 | @Override 79 | protected float getWidth(float scale, boolean example) { 80 | return RenderUtils.getStringWidth(longestString) * scale + 2 * padding * scale; 81 | } 82 | 83 | @Override 84 | protected float getHeight(float scale, boolean example) { 85 | try { 86 | return ((Minecraft.getMinecraft().fontRendererObj.FONT_HEIGHT 87 | * (BazaarNotifier.config.craftingModule.craftingListLength + 1) 88 | + (BazaarNotifier.config.craftingModule.craftingListLength + 1) * 2) * scale - 2) 89 | + 2 * scale * padding; 90 | } catch (NullPointerException e) { 91 | return 1; 92 | } 93 | } 94 | 95 | 96 | private ArrayList generateHelperLine() { 97 | if (Mouse.isButtonDown(1) && getMouseCoordinateY() > position.getY() - 1 98 | && getMouseCoordinateY() 99 | < position.getY() + (Minecraft.getMinecraft().fontRendererObj.FONT_HEIGHT + 1) * scale) { 100 | int[] width = new int[4]; 101 | int totalWidth = 0; 102 | int relativeX = getMouseCoordinateX() - (int) position.getX(); 103 | width[0] = 104 | (int) (Minecraft.getMinecraft().fontRendererObj.getStringWidth( 105 | BazaarNotifier.config.craftingModule.useBuyOrders ? " Profits (Buy Orders) -" 106 | : " Profits (Instant Buy) -") * scale); 107 | width[1] = BazaarNotifier.config.craftingModule.showInstantSellProfit ? 108 | (int) (Minecraft.getMinecraft().fontRendererObj.getStringWidth(" Instant Sell ") * scale) 109 | : 0; 110 | width[2] = BazaarNotifier.config.craftingModule.showSellOfferProfit ? 111 | (int) (Minecraft.getMinecraft().fontRendererObj.getStringWidth("/ Sell Offer ") * scale) 112 | : 0; 113 | width[3] = BazaarNotifier.config.craftingModule.showProfitPerMil ? 114 | (int) (Minecraft.getMinecraft().fontRendererObj.getStringWidth("/ Profit %") * scale) 115 | : 0; 116 | 117 | for (int i : width) { 118 | totalWidth += i; 119 | } 120 | 121 | for (int i = 3; i >= 0; i--) { 122 | if (totalWidth > relativeX && totalWidth - width[i] < relativeX && inMovementBox() 123 | && BazaarNotifier.inBazaar) { 124 | switch (i) { 125 | case 0: 126 | if (mouseButtonDown) { 127 | BazaarNotifier.config.craftingModule.useBuyOrders ^= true; 128 | mouseButtonDown = false; 129 | } 130 | break; 131 | case 1: 132 | BazaarNotifier.config.craftingModule.craftingSortingOption = 0; 133 | break; 134 | case 2: 135 | BazaarNotifier.config.craftingModule.craftingSortingOption = 1; 136 | break; 137 | case 3: 138 | BazaarNotifier.config.craftingModule.craftingSortingOption = 2; 139 | break; 140 | } 141 | CraftingCalculator.sort(); 142 | break; 143 | } 144 | totalWidth -= width[i]; 145 | } 146 | } else if (!Mouse.isButtonDown(1)) { 147 | mouseButtonDown = true; 148 | } 149 | ArrayList helperLine = new ArrayList<>(); 150 | helperLine.add(new ColoredText(" ", BazaarNotifier.config.infoColor.toJavaColor())); 151 | helperLine 152 | .add(new ColoredText( 153 | BazaarNotifier.config.craftingModule.useBuyOrders ? "Profits (Buy Orders)" 154 | : "Profits (Instant Buy)", 155 | BazaarNotifier.config.infoColor.toJavaColor())); 156 | if (BazaarNotifier.config.craftingModule.showProfitPerMil || BazaarNotifier.config 157 | .craftingModule.showInstantSellProfit 158 | || BazaarNotifier.config.craftingModule.showSellOfferProfit) { 159 | helperLine.add(new ColoredText(" - ", BazaarNotifier.config.infoColor.toJavaColor())); 160 | } 161 | if (BazaarNotifier.config.craftingModule.showInstantSellProfit) { 162 | helperLine.add(new ColoredText(" Instant Sell", 163 | BazaarNotifier.config.craftingModule.craftingSortingOption == 0 ? 164 | new Color(141, 152, 201) : BazaarNotifier.config.infoColor.toJavaColor())); 165 | if (BazaarNotifier.config.craftingModule.showSellOfferProfit 166 | || BazaarNotifier.config.craftingModule.showProfitPerMil) { 167 | helperLine.add(new ColoredText(" /", BazaarNotifier.config.infoColor.toJavaColor())); 168 | } 169 | } 170 | if (BazaarNotifier.config.craftingModule.showSellOfferProfit) { 171 | helperLine.add(new ColoredText(" Sell Offer", 172 | BazaarNotifier.config.craftingModule.craftingSortingOption == 1 ? 173 | new Color(141, 152, 201) : BazaarNotifier.config.infoColor.toJavaColor())); 174 | if (BazaarNotifier.config.craftingModule.showProfitPerMil) { 175 | helperLine.add(new ColoredText(" /", BazaarNotifier.config.infoColor.toJavaColor())); 176 | } 177 | } 178 | if (BazaarNotifier.config.craftingModule.showProfitPerMil) { 179 | helperLine.add(new ColoredText(" Profit %", 180 | BazaarNotifier.config.craftingModule.craftingSortingOption == 2 ? 181 | new Color(141, 152, 201) : BazaarNotifier.config.infoColor.toJavaColor())); 182 | } 183 | 184 | return helperLine; 185 | } 186 | 187 | @Override 188 | public void draw() { 189 | GL11.glTranslated(0, 0, 1); 190 | drawBounds(); 191 | if (BazaarNotifier.bazaarDataRaw != null) { 192 | ArrayList> items = new ArrayList<>(); 193 | items.add(generateHelperLine()); 194 | for (int i = shift; i < BazaarNotifier.config.craftingModule.craftingListLength + shift; 195 | i++) { 196 | ArrayList message = new ArrayList<>(); 197 | if (i < list.size()) { 198 | if (list.get(i).length != 0) { 199 | Double profitInstaSell = Double.valueOf(list.get(i)[BazaarNotifier.config.craftingModule.useBuyOrders ? 0 : 3]); 200 | Double profitSellOffer = Double.valueOf(list.get(i)[BazaarNotifier.config.craftingModule.useBuyOrders ? 1 : 4]); 201 | Double profitPercentage = Double.valueOf(list.get(i)[BazaarNotifier.config.craftingModule.useBuyOrders ? 2 : 5]); 202 | String itemName = list.get(i)[6]; 203 | 204 | String itemNameConverted = BazaarNotifier.bazaarConv.get(itemName); 205 | message.add(new ColoredText(String.valueOf(i + 1), 206 | BazaarNotifier.config.numberColor.toJavaColor())); 207 | message.add(new ColoredText(". ", BazaarNotifier.config.numberColor.toJavaColor())); 208 | message.add( 209 | new ColoredText(itemNameConverted, BazaarNotifier.config.itemColor.toJavaColor())); 210 | 211 | if (BazaarNotifier.config.craftingModule.showProfitPerMil 212 | || BazaarNotifier.config.craftingModule.showInstantSellProfit 213 | || BazaarNotifier.config.craftingModule.showSellOfferProfit) { 214 | message.add(new ColoredText(" - ", BazaarNotifier.config.infoColor.toJavaColor())); 215 | } 216 | 217 | if (BazaarNotifier.config.craftingModule.showInstantSellProfit) { 218 | message.add(new ColoredText(BazaarNotifier.df.format(profitInstaSell), 219 | getColor(profitInstaSell.intValue()))); 220 | 221 | if (BazaarNotifier.config.craftingModule.showSellOfferProfit) { 222 | message.add(new ColoredText(" / ", BazaarNotifier.config.infoColor.toJavaColor())); 223 | } 224 | } 225 | 226 | if (BazaarNotifier.config.craftingModule.showSellOfferProfit) { 227 | message.add(new ColoredText(BazaarNotifier.df.format(profitSellOffer), 228 | getColor(profitSellOffer.intValue()))); 229 | 230 | if (BazaarNotifier.config.craftingModule.showProfitPerMil) { 231 | message.add(new ColoredText(" / ", BazaarNotifier.config.infoColor.toJavaColor())); 232 | } 233 | } 234 | 235 | if (BazaarNotifier.config.craftingModule.showProfitPerMil) { 236 | message.add(new ColoredText(BazaarNotifier.df.format(profitPercentage) + "%", 237 | getColorForPercentage(profitPercentage.intValue()))); 238 | } 239 | } else { 240 | message.add(new ColoredText("Error, just wait", Color.RED)); 241 | } 242 | items.add(message); 243 | } 244 | } 245 | longestString = RenderUtils.getLongestString(items); 246 | 247 | RenderUtils.drawColorfulParagraph(items, (int) position.getX() + padding, (int) position.getY() + padding, scale); 248 | if (BazaarNotifier.inBazaar) { 249 | renderMaterials(checkHoveredText(), list); 250 | } 251 | } else { 252 | RenderUtils.drawCenteredString("Waiting for bazaar data", (int) position.getX(), 253 | (int) position.getY(), 0xAAAAAA, scale); 254 | //Todo add height and width 255 | } 256 | GL11.glTranslated(0, 0, -1); 257 | } 258 | 259 | protected Color getColor(int price) { 260 | if (price <= 0) { 261 | return Color.RED; 262 | } else if (price <= 5000) { 263 | return Color.YELLOW; 264 | } else { 265 | return Color.GREEN; 266 | } 267 | } 268 | 269 | protected Color getColorForPercentage(int price) { 270 | if (price <= 0) { 271 | return Color.RED; 272 | } else if (price <= 10) { 273 | return Color.YELLOW; 274 | } else { 275 | return Color.GREEN; 276 | } 277 | } 278 | 279 | @Override 280 | protected void reset() { 281 | position.setPosition(Defaults.CRAFTING_MODULE_X, Defaults.CRAFTING_MODULE_Y); 282 | setScale(1, false); 283 | enabled = true; 284 | BazaarNotifier.config.craftingModule.craftingListLength = Defaults.CRAFTING_LIST_LENGTH; 285 | } 286 | 287 | @Override 288 | public String name() { 289 | return ModuleName.CRAFTING.name(); 290 | } 291 | 292 | @Override 293 | protected boolean shouldDrawBounds() { 294 | return true; 295 | } 296 | 297 | @Override 298 | protected int getMaxShift() { 299 | return list.size() - BazaarNotifier.config.craftingModule.craftingListLength; 300 | } 301 | 302 | protected int checkHoveredText() { 303 | float _y = position.getY() +(-padding + 11) * scale; 304 | float y2 = _y + ((BazaarNotifier.config.craftingModule.craftingListLength) * 11 * scale); 305 | int mouseYFormatted = getMouseCoordinateY(); 306 | float relativeYMouse = (mouseYFormatted - _y) / (11 * scale); 307 | if (getWidth(scale, false) != 0) { 308 | if (inMovementBox() && mouseYFormatted >= _y && mouseYFormatted <= y2) { 309 | return (int) relativeYMouse + shift; 310 | } else { 311 | return -1; 312 | } 313 | } else { 314 | return 1; 315 | } 316 | } 317 | 318 | protected void renderMaterials(int hoveredText, ArrayList list) { 319 | checkMouseMovement(); 320 | ArrayList> material = new ArrayList<>(); 321 | ArrayList text = new ArrayList<>(); 322 | 323 | if (hoveredText > -1) { 324 | if (hoveredText < list.size()) { 325 | 326 | int materialCount; 327 | materialCount = BazaarNotifier.enchantCraftingList.getAsJsonObject("other") 328 | .getAsJsonObject(list.get(hoveredText)[6]).getAsJsonArray("material").size(); 329 | for (int b = 0; b < materialCount / 2; b++) { 330 | StringBuilder _material = new StringBuilder(); 331 | if (b == 0) { 332 | _material.append((BazaarNotifier.enchantCraftingList.getAsJsonObject("other") 333 | .getAsJsonObject(list.get(hoveredText)[6]).getAsJsonArray("material").get(1) 334 | .getAsInt() 335 | * mouseWheelShift)).append("x ").append(BazaarNotifier.bazaarConv 336 | .get(BazaarNotifier.enchantCraftingList.getAsJsonObject("other") 337 | .getAsJsonObject(list.get(hoveredText)[6]).getAsJsonArray("material") 338 | .get(0).getAsString())); 339 | } else { 340 | _material.append(" | ").append( 341 | BazaarNotifier.enchantCraftingList.getAsJsonObject("other") 342 | .getAsJsonObject(list.get(hoveredText)[6]).getAsJsonArray("material") 343 | .get(b * 2 + 1).getAsInt() * mouseWheelShift).append("x ").append( 344 | BazaarNotifier.bazaarConv.get( 345 | BazaarNotifier.enchantCraftingList.getAsJsonObject("other") 346 | .getAsJsonObject(list.get(hoveredText)[6]).getAsJsonArray("material") 347 | .get(b * 2).getAsString())); 348 | } 349 | 350 | text.add(new ColoredText(_material.toString(), Color.LIGHT_GRAY)); 351 | } 352 | material.add(text); 353 | int longestXString = RenderUtils.drawColorfulParagraph(material, getMouseCoordinateX(), 354 | getMouseCoordinateY() - (int) (8 * scale), scale); 355 | Gui.drawRect(getMouseCoordinateX() - padding, 356 | getMouseCoordinateY() - (int) (8 * scale) - (int) (padding * scale), 357 | (int) (getMouseCoordinateX() + longestXString + padding * scale), 358 | (int) (getMouseCoordinateY() + padding * scale), 0xFF404040); 359 | RenderUtils.drawColorfulParagraph(material, getMouseCoordinateX(), 360 | getMouseCoordinateY() - (int) (8 * scale), scale); 361 | } 362 | } 363 | } 364 | 365 | 366 | public void checkMouseMovement() { 367 | if (lastHovered != checkHoveredText()) { 368 | mouseWheelShift = 1; 369 | } 370 | lastHovered = checkHoveredText(); 371 | } 372 | } -------------------------------------------------------------------------------- /src/main/java/dev/meyi/bn/modules/module/NotificationModule.java: -------------------------------------------------------------------------------- 1 | package dev.meyi.bn.modules.module; 2 | 3 | import cc.polyfrost.oneconfig.libs.universal.UMatrixStack; 4 | import dev.meyi.bn.BazaarNotifier; 5 | import dev.meyi.bn.json.Order; 6 | import dev.meyi.bn.modules.Module; 7 | import dev.meyi.bn.modules.ModuleName; 8 | import dev.meyi.bn.utilities.ColoredText; 9 | import dev.meyi.bn.utilities.Defaults; 10 | import dev.meyi.bn.utilities.ReflectionHelper; 11 | import dev.meyi.bn.utilities.RenderUtils; 12 | import dev.meyi.bn.utilities.Utils; 13 | import java.awt.Color; 14 | import java.util.ArrayList; 15 | import java.util.List; 16 | import net.minecraft.client.Minecraft; 17 | import net.minecraft.client.gui.Gui; 18 | import net.minecraft.client.gui.ScaledResolution; 19 | import net.minecraft.client.gui.inventory.GuiChest; 20 | import net.minecraft.inventory.IInventory; 21 | import net.minecraft.item.Item; 22 | import net.minecraft.item.ItemStack; 23 | import net.minecraft.util.StringUtils; 24 | import org.apache.commons.lang3.text.WordUtils; 25 | import org.lwjgl.opengl.GL11; 26 | 27 | public class NotificationModule extends Module { 28 | 29 | public transient static final ModuleName type = ModuleName.NOTIFICATION; 30 | 31 | public NotificationModule() { 32 | super(); 33 | } 34 | 35 | @Override 36 | protected void draw(UMatrixStack matrices, float x, float y, float scale, boolean example) { 37 | draw(); 38 | } 39 | 40 | @Override 41 | protected float getWidth(float scale, boolean example) { 42 | if (longestString != null) { 43 | if (!longestString.isEmpty()) { 44 | return RenderUtils.getStringWidth(longestString) * scale + 2 * padding * scale; 45 | } 46 | } 47 | return 200*scale; 48 | } 49 | 50 | @Override 51 | protected float getHeight(float scale, boolean example) { 52 | return ((Minecraft.getMinecraft().fontRendererObj.FONT_HEIGHT * 10 + 20) * scale - 2) + 2 * padding * scale; 53 | } 54 | //source dsm 55 | public static void drawOnSlot(int chestSize, int slot, int color) { 56 | chestSize += 36; 57 | ScaledResolution sr = new ScaledResolution(Minecraft.getMinecraft()); 58 | int guiLeft = (sr.getScaledWidth() - 176) / 2; 59 | int guiTop = (sr.getScaledHeight() - 222) / 2; 60 | int xSlotPos = (slot % 9) * 18 + 8; 61 | int ySlotPos = slot / 9; 62 | ySlotPos = ySlotPos * 18 + 18; 63 | int x = guiLeft + xSlotPos; 64 | int y = guiTop + ySlotPos; 65 | // Move down when chest isn't 6 rows 66 | if (chestSize != 90) { 67 | y += (6 - (chestSize - 36) / 9) * 9; 68 | } 69 | GL11.glTranslated(0, 0, 1); 70 | Gui.drawRect(x, y, x + 16, y + 16, color); 71 | GL11.glTranslated(0, 0, -1); 72 | } 73 | 74 | @Override 75 | public void draw() { 76 | GL11.glTranslated(0, 0, 1); 77 | // add extra space after "Buy" so it lines up with sell 78 | drawBounds(); 79 | ArrayList> items = new ArrayList<>(); 80 | 81 | if (BazaarNotifier.orders.size() != 0) { 82 | 83 | int size = Math.min(shift + 10, BazaarNotifier.orders.size()); 84 | 85 | for (int i = shift; i < size; i++) { 86 | Order currentOrder = BazaarNotifier.orders.get(i); 87 | ArrayList message = new ArrayList<>(); 88 | 89 | Color statusSpecificColor = currentOrder.orderStatus == Order.OrderStatus.BEST 90 | || currentOrder.orderStatus == Order.OrderStatus.SEARCHING 91 | ? Color.GREEN : currentOrder.orderStatus == Order.OrderStatus.MATCHED? 92 | Color.YELLOW : Color.RED; 93 | Color typeSpecificColor = currentOrder.type == Order.OrderType.BUY?new Color( 90, 0, 250):Color.CYAN; 94 | 95 | message.add(new ColoredText(i+1 + ". ", BazaarNotifier.config.numberColor.toJavaColor())); 96 | message.add(new ColoredText(WordUtils.capitalizeFully(currentOrder.type.name()),typeSpecificColor)); 97 | message.add(new ColoredText(" - ", BazaarNotifier.config.infoColor.toJavaColor())); 98 | message.add(new ColoredText(BazaarNotifier.dfNoDecimal.format(currentOrder.startAmount)+ "x ", 99 | BazaarNotifier.config.itemColor.toJavaColor())); 100 | message.add(new ColoredText(currentOrder.product , BazaarNotifier.config.itemColor.toJavaColor())); 101 | message.add(new ColoredText(" - ", BazaarNotifier.config.infoColor.toJavaColor())); 102 | message.add(new ColoredText(currentOrder.orderStatus.name() + " ", statusSpecificColor)); 103 | 104 | 105 | items.add(message); 106 | } 107 | longestString = RenderUtils.getLongestString(items); 108 | RenderUtils.drawColorfulParagraph(items, (int)position.getX() + padding, (int)position.getY() + padding, scale); 109 | } else { 110 | longestString = ""; 111 | RenderUtils.drawCenteredString("No orders found", (int)position.getX(), (int)position.getY(), 0xAAAAAA, scale); 112 | } 113 | highlightOrder(checkHoveredText()); 114 | GL11.glTranslated(0, 0, -1); 115 | } 116 | 117 | @Override 118 | protected void reset() { 119 | position.setPosition(Defaults.NOTIFICATION_MODULE_X, Defaults.NOTIFICATION_MODULE_Y); 120 | setScale(1, false); 121 | enabled = true; 122 | } 123 | 124 | @Override 125 | public String name() { 126 | return ModuleName.NOTIFICATION.name(); 127 | } 128 | 129 | @Override 130 | protected boolean shouldDrawBounds() { 131 | return true; 132 | } 133 | 134 | @Override 135 | protected int getMaxShift() { 136 | return BazaarNotifier.orders.size() - 10; 137 | } 138 | 139 | protected int checkHoveredText() { 140 | float _y = position.getY(); 141 | float y2 = _y + (10 * 11 * scale); 142 | int mouseYFormatted = getMouseCoordinateY(); 143 | int mouseXFormatted = getMouseCoordinateX(); 144 | float relativeYMouse = (mouseYFormatted - _y) / (11 * scale); 145 | if (this.getWidth(scale, false) != 0) { 146 | if (inMovementBox() && mouseYFormatted >= _y && mouseYFormatted <= y2 - 3 * scale) { 147 | return Math.round(relativeYMouse + shift); 148 | } else { 149 | return -1; 150 | } 151 | } else { 152 | return -1; 153 | } 154 | } 155 | 156 | public void highlightOrder(int hoveredText) { 157 | if (BazaarNotifier.orders.size() <= hoveredText || hoveredText == -1) { 158 | return; 159 | } 160 | 161 | if (Minecraft.getMinecraft().currentScreen instanceof GuiChest && BazaarNotifier.inBazaar 162 | && BazaarNotifier.activeBazaar) { 163 | IInventory chest = ReflectionHelper.getLowerChestInventory( 164 | (GuiChest) Minecraft.getMinecraft().currentScreen); 165 | if (chest == null) { 166 | return; 167 | } 168 | String chestName = chest.getDisplayName().getUnformattedText().toLowerCase(); 169 | 170 | if (chestName.contains("bazaar orders")) { 171 | ItemStack[] items = new ItemStack[chest.getSizeInventory()]; 172 | for (int i = 0; i < chest.getSizeInventory(); i++) { 173 | items[i] = chest.getStackInSlot(i); 174 | } 175 | 176 | for (int j = 0; j < items.length; j++) { 177 | ItemStack item = items[j]; 178 | 179 | if (item == null 180 | || Item.itemRegistry.getIDForObject(item.getItem()) == 160 181 | || Item.itemRegistry.getIDForObject(item.getItem()) == 102 182 | || Item.itemRegistry.getIDForObject(item.getItem()) == 154 183 | || Item.itemRegistry.getIDForObject(item.getItem()) == 262) { 184 | continue; 185 | } 186 | String itemDisplayName = StringUtils.stripControlCodes(item.getDisplayName()); 187 | Order.OrderType type = itemDisplayName.split(" ")[0] 188 | .equalsIgnoreCase("sell") ? Order.OrderType.SELL : Order.OrderType.BUY; 189 | String product = itemDisplayName 190 | .replaceAll("SELL ", "").replaceAll("BUY ", ""); 191 | List lore = Utils.getLoreFromItemStack(item); 192 | 193 | int amount = Integer.parseInt( 194 | lore.get(2).toLowerCase().split("amount: ")[1].replaceAll("[x,.]", "")); 195 | 196 | String ppu = lore.get(3).equals("") ? lore.get(4) : lore.get(5); 197 | ppu = ppu.toLowerCase().replace("price per unit: ", "").replace(" coins", "") 198 | .replaceAll(",", ""); 199 | 200 | if (!ppu.contains("expired!") && !ppu.contains("expires in")) { 201 | 202 | double pricePerUnit = Double.parseDouble(ppu); 203 | 204 | Order o = new Order(product, type, pricePerUnit, amount); 205 | 206 | for (int i = 0; i < BazaarNotifier.orders.size(); i++) { 207 | if (o.matches(BazaarNotifier.orders.get(hoveredText))) { 208 | drawOnSlot(chest.getSizeInventory(), j, 0xff00ff00); 209 | break; 210 | } 211 | } 212 | } 213 | } 214 | } 215 | } 216 | } 217 | } 218 | -------------------------------------------------------------------------------- /src/main/java/dev/meyi/bn/modules/module/SuggestionModule.java: -------------------------------------------------------------------------------- 1 | package dev.meyi.bn.modules.module; 2 | 3 | import cc.polyfrost.oneconfig.config.annotations.Slider; 4 | import cc.polyfrost.oneconfig.config.annotations.Switch; 5 | import cc.polyfrost.oneconfig.config.migration.JsonName; 6 | import cc.polyfrost.oneconfig.libs.universal.UMatrixStack; 7 | import dev.meyi.bn.BazaarNotifier; 8 | import dev.meyi.bn.modules.Module; 9 | import dev.meyi.bn.modules.ModuleName; 10 | import dev.meyi.bn.utilities.ColoredText; 11 | import dev.meyi.bn.utilities.RenderUtils; 12 | import dev.meyi.bn.utilities.Defaults; 13 | import java.awt.Color; 14 | import java.util.ArrayList; 15 | import java.util.LinkedList; 16 | import java.util.List; 17 | import net.minecraft.client.Minecraft; 18 | import org.lwjgl.opengl.GL11; 19 | 20 | public class SuggestionModule extends Module{ 21 | 22 | public transient static final ModuleName type = ModuleName.SUGGESTION; 23 | public transient static List list = new LinkedList<>(); 24 | 25 | @JsonName("suggestionListLength") 26 | @Slider(name = "Suggestion List Entries", 27 | category = "Suggestion Module", 28 | description = "The amount of entries in the Suggestion Module list", 29 | min = 1,max = 25,step = 1 30 | ) 31 | public int suggestionListLength = Defaults.SUGGESTION_LIST_LENGTH; 32 | 33 | @Switch(name= "Use Profit per Hour", category = "Suggestion Module") 34 | public boolean useProfitPerHour = false; 35 | 36 | @JsonName("suggestionShowEnchantments") 37 | @Switch(name = "Show Enchantments", 38 | category = "Suggestion Module", 39 | description = "If the mod should recommend enchantments" 40 | ) 41 | public boolean suggestionShowEnchantments = Defaults.SUGGESTION_SHOW_ENCHANTMENTS; 42 | 43 | public SuggestionModule() { 44 | super(); 45 | } 46 | 47 | @Override 48 | protected void draw(UMatrixStack matrices, float x, float y, float scale, boolean example) { 49 | draw(); 50 | } 51 | 52 | @Override 53 | protected float getWidth(float scale, boolean example) { 54 | return RenderUtils.getStringWidth(longestString)*scale + 2 * padding * scale; 55 | } 56 | 57 | @Override 58 | protected float getHeight(float scale, boolean example) { 59 | if(BazaarNotifier.config == null){ 60 | return 100f*scale; 61 | } 62 | return (Minecraft.getMinecraft().fontRendererObj.FONT_HEIGHT 63 | * BazaarNotifier.config.suggestionModule.suggestionListLength 64 | + BazaarNotifier.config.suggestionModule.suggestionListLength * 2)*scale - 2 65 | + 2 * padding * scale; 66 | } 67 | 68 | @Override 69 | public void draw() { 70 | GL11.glTranslated(0, 0, 1); 71 | drawBounds(); 72 | if (list.size() != 0) { 73 | ArrayList> items = new ArrayList<>(); 74 | for (int i = shift; i < BazaarNotifier.config.suggestionModule.suggestionListLength + shift; i++) { 75 | ArrayList message = new ArrayList<>(); 76 | message.add(new ColoredText((i + 1) + ". ", BazaarNotifier.config.numberColor.toJavaColor())); 77 | message.add(new ColoredText(list.get(i)[0], BazaarNotifier.config.itemColor.toJavaColor())); 78 | message.add(new ColoredText(" - ", BazaarNotifier.config.infoColor.toJavaColor())); 79 | message.add(new ColoredText("EP: ", Color.RED)); 80 | message.add(new ColoredText("" + BazaarNotifier.df.format(Double.parseDouble(list.get(i)[1]) * 81 | (useProfitPerHour?60:1)), Color.ORANGE)); 82 | items.add(message); 83 | } 84 | longestString = RenderUtils.getLongestString(items); 85 | RenderUtils.drawColorfulParagraph(items, (int)position.getX() + padding, (int)position.getY() + padding, scale); 86 | } else { 87 | RenderUtils.drawCenteredString("Waiting for bazaar data", (int)position.getX(), (int)position.getY(), 0xAAAAAA, scale); 88 | //Todo add height and width 89 | } 90 | GL11.glTranslated(0, 0, -1); 91 | } 92 | 93 | @Override 94 | protected void reset() { 95 | position.setPosition(Defaults.SUGGESTION_MODULE_X,Defaults.SUGGESTION_MODULE_Y); 96 | scale = 1; 97 | enabled = true; 98 | BazaarNotifier.config.suggestionModule.suggestionListLength = Defaults.SUGGESTION_LIST_LENGTH; 99 | } 100 | 101 | @Override 102 | public String name() { 103 | return ModuleName.SUGGESTION.name(); 104 | } 105 | 106 | @Override 107 | protected boolean shouldDrawBounds() { 108 | return true; 109 | } 110 | 111 | @Override 112 | protected int getMaxShift() { 113 | return list.size() - BazaarNotifier.config.suggestionModule.suggestionListLength; 114 | } 115 | 116 | } 117 | 118 | 119 | -------------------------------------------------------------------------------- /src/main/java/dev/meyi/bn/utilities/ColoredText.java: -------------------------------------------------------------------------------- 1 | package dev.meyi.bn.utilities; 2 | 3 | import java.awt.Color; 4 | 5 | public class ColoredText { 6 | public String string; 7 | public Color color; 8 | 9 | public ColoredText(String string, Color color){ 10 | this.string = string; 11 | this.color = color; 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/dev/meyi/bn/utilities/Defaults.java: -------------------------------------------------------------------------------- 1 | package dev.meyi.bn.utilities; 2 | 3 | 4 | public class Defaults { 5 | 6 | public static final int SUGGESTION_MODULE_X = 5; 7 | public static final int SUGGESTION_MODULE_Y = 10; 8 | public static final int BANK_MODULE_X = 5; 9 | public static final int BANK_MODULE_Y = 10; 10 | public static final int NOTIFICATION_MODULE_X = 5; 11 | public static final int NOTIFICATION_MODULE_Y = 10; 12 | public static final int CRAFTING_MODULE_X = 5; 13 | public static final int CRAFTING_MODULE_Y = 10; 14 | public static final int CRAFTING_LIST_LENGTH = 10; 15 | public static final int SUGGESTION_LIST_LENGTH = 10; 16 | public static final int CRAFTING_SORTING_OPTION = 0; 17 | public static final boolean INSTANT_SELL_PROFIT = true; 18 | public static final boolean SELL_OFFER_PROFIT = true; 19 | public static final boolean PROFIT_PER_MIL = true; 20 | public static final boolean COLLECTION_CHECKING = false; 21 | public static final boolean SEND_CHAT_MESSAGES = true; 22 | public static final boolean USE_BUY_ORDERS = true; 23 | public static final boolean SUGGESTION_SHOW_ENCHANTMENTS = true; 24 | public static final boolean BANK_RAW_DIFFERENCE = false; 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/dev/meyi/bn/utilities/ReflectionHelper.java: -------------------------------------------------------------------------------- 1 | package dev.meyi.bn.utilities; 2 | 3 | import java.lang.reflect.Field; 4 | 5 | import net.minecraft.client.gui.inventory.GuiChest; 6 | import net.minecraft.inventory.IInventory; 7 | import net.minecraft.launchwrapper.Launch; 8 | 9 | public class ReflectionHelper { 10 | private static Field lowerChestInventory; 11 | public static void setup() { 12 | try { 13 | lowerChestInventory = GuiChest.class.getDeclaredField((Boolean) Launch.blackboard.get("fml.deobfuscatedEnvironment") ? "lowerChestInventory" : "field_147015_w"); 14 | lowerChestInventory.setAccessible(true); 15 | } catch (NoSuchFieldException e) { 16 | try{ 17 | lowerChestInventory = GuiChest.class.getDeclaredField("lowerChestInventory"); 18 | lowerChestInventory.setAccessible(true); 19 | }catch (NoSuchFieldException ignored){ 20 | lowerChestInventory = null; 21 | e.printStackTrace(); 22 | } 23 | } 24 | } 25 | 26 | public static IInventory getLowerChestInventory(GuiChest g) { 27 | try { 28 | if (lowerChestInventory != null) { 29 | return (IInventory)lowerChestInventory.get(g); 30 | } 31 | } catch (IllegalAccessException ignored) { 32 | } 33 | return null; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/dev/meyi/bn/utilities/RenderUtils.java: -------------------------------------------------------------------------------- 1 | package dev.meyi.bn.utilities; 2 | 3 | import dev.meyi.bn.BazaarNotifier; 4 | import dev.meyi.bn.json.Order; 5 | 6 | import java.util.ArrayList; 7 | import java.util.List; 8 | 9 | import net.minecraft.client.Minecraft; 10 | import net.minecraft.client.gui.FontRenderer; 11 | import net.minecraft.util.ChatComponentText; 12 | import net.minecraft.util.EnumChatFormatting; 13 | import org.lwjgl.opengl.GL11; 14 | 15 | 16 | public class RenderUtils { 17 | 18 | 19 | /** 20 | * @param renderer Minecraft's renderer 21 | * @param x horizontal coordinate for rendering 22 | * @param y vertical coordinate for rendering 23 | * @param message text split up by color in order of rendering 24 | * @param dropShadow should the shadow have text 25 | * @return length of the entire text 26 | */ 27 | public static int drawMulticoloredString(FontRenderer renderer, int x, int y, 28 | List message, boolean dropShadow, float moduleScale) { 29 | 30 | int renderLength = 0; 31 | for (ColoredText substring : message) { 32 | GL11.glScalef(moduleScale, moduleScale, 1); 33 | renderer.drawString(substring.string, (x + renderLength) / moduleScale, y / moduleScale, 34 | substring.color.getRGB(), 35 | dropShadow); 36 | renderLength += renderer.getStringWidth(substring.string) * moduleScale; 37 | GL11.glScalef((float) Math.pow(moduleScale, -1), (float) Math.pow(moduleScale, -1), 1); 38 | } 39 | 40 | return renderLength; 41 | } 42 | 43 | public static int drawColorfulParagraph(ArrayList> items, int x, int y, 44 | float moduleScale) { 45 | float longestXString = 0; 46 | for (int i = 0; i < items.size(); i++) { 47 | float fontHeight = 48 | (Minecraft.getMinecraft().fontRendererObj.FONT_HEIGHT + 2) * i * moduleScale; 49 | int length = RenderUtils 50 | .drawMulticoloredString(Minecraft.getMinecraft().fontRendererObj, 51 | x, y 52 | + (int) fontHeight, 53 | items.get(i), false, moduleScale); 54 | if (length > longestXString) { 55 | longestXString = length; 56 | } 57 | } 58 | return (int) longestXString; 59 | } 60 | public static String getLongestString(ArrayList> items) { 61 | FontRenderer fontRenderer = Minecraft.getMinecraft().fontRendererObj; 62 | float longestXString = 0; 63 | String longestString = ""; 64 | for (List item : items) { 65 | StringBuilder builder = new StringBuilder(""); 66 | for (ColoredText entry : item) { 67 | builder.append(entry.string); 68 | } 69 | 70 | int length = fontRenderer.getStringWidth(builder.toString()); 71 | if (length > longestXString) { 72 | longestXString = length; 73 | longestString = builder.toString(); 74 | } 75 | } 76 | return longestString; 77 | } 78 | public static int getStringWidth(String string) { 79 | FontRenderer fontRenderer = Minecraft.getMinecraft().fontRendererObj; 80 | return fontRenderer.getStringWidth(string); 81 | } 82 | 83 | public static void chatNotification(Order order, String notification) { 84 | if (!BazaarNotifier.config.showChatMessages) { 85 | return; 86 | } 87 | EnumChatFormatting messageColor = 88 | (notification.equalsIgnoreCase("REVIVED") ? EnumChatFormatting.GREEN 89 | : order.type.equals(Order.OrderType.BUY) ? EnumChatFormatting.DARK_PURPLE 90 | : EnumChatFormatting.BLUE); 91 | if (Minecraft.getMinecraft().thePlayer != null) { 92 | Minecraft.getMinecraft().thePlayer.addChatMessage(new ChatComponentText( 93 | messageColor + order.type.longName 94 | + EnumChatFormatting.GRAY + " for " 95 | + messageColor + BazaarNotifier.dfNoDecimal 96 | .format(order.startAmount) 97 | + EnumChatFormatting.GRAY + "x " + messageColor 98 | + order.product 99 | + EnumChatFormatting.YELLOW 100 | + " " + notification + " " + EnumChatFormatting.GRAY + "(" 101 | + messageColor + BazaarNotifier.df.format(order.pricePerUnit) 102 | + EnumChatFormatting.GRAY + ")" 103 | )); 104 | } 105 | } 106 | 107 | public static void drawCenteredString(String text, int x, int y, int color, float moduleScale) { 108 | x = (int) (x / moduleScale + 200 / 4); 109 | y = (int) (y / moduleScale + (Minecraft.getMinecraft().fontRendererObj.FONT_HEIGHT * 6)); 110 | GL11.glScalef(moduleScale, moduleScale, 1); 111 | Minecraft.getMinecraft().fontRendererObj.drawString(text, x, y, color); 112 | GL11.glScalef((float) Math.pow(moduleScale, -1), (float) Math.pow(moduleScale, -1), 1); 113 | } 114 | } 115 | 116 | -------------------------------------------------------------------------------- /src/main/java/dev/meyi/bn/utilities/ScheduledEvents.java: -------------------------------------------------------------------------------- 1 | package dev.meyi.bn.utilities; 2 | 3 | import dev.meyi.bn.BazaarNotifier; 4 | import dev.meyi.bn.json.Order; 5 | import dev.meyi.bn.modules.calc.CraftingCalculator; 6 | import dev.meyi.bn.modules.calc.SuggestionCalculator; 7 | import java.text.DateFormat; 8 | import java.util.Date; 9 | import java.util.HashMap; 10 | import java.util.Map; 11 | import java.util.Map.Entry; 12 | import java.util.concurrent.Executors; 13 | import java.util.concurrent.ScheduledExecutorService; 14 | import java.util.concurrent.TimeUnit; 15 | import net.minecraft.client.Minecraft; 16 | import net.minecraft.util.ChatComponentText; 17 | import net.minecraft.util.EnumChatFormatting; 18 | 19 | public class ScheduledEvents { 20 | 21 | public static ScheduledEvents instance; 22 | 23 | private final Map executors = new HashMap<>(); 24 | 25 | private ScheduledEvents() { 26 | executors.put("bazaar", getScheduler("bazaar")); 27 | executors.put("notification", getScheduler("notification")); 28 | executors.put("crafting", getScheduler("crafting")); 29 | executors.put("suggestion", getScheduler("suggestion")); 30 | executors.put("collection", getScheduler("collection")); 31 | executors.put("purse", getScheduler("purse")); 32 | 33 | shutdownWatcher(); 34 | } 35 | 36 | public static void create() { 37 | CraftingCalculator.getUnlockedRecipes(); 38 | if (instance == null) { 39 | instance = new ScheduledEvents(); 40 | } 41 | } 42 | 43 | private ScheduledExecutorService getScheduler(String key) { 44 | switch (key) { 45 | case "bazaar": 46 | return getBazaarData(); 47 | case "notification": 48 | return notificationLoop(); 49 | case "crafting": 50 | return craftingBankLoop(); 51 | case "suggestion": 52 | return suggestionLoop(); 53 | case "collection": 54 | return collectionLoop(); 55 | } 56 | return null; 57 | } 58 | 59 | private void shutdownWatcher() { 60 | Executors.newScheduledThreadPool(1).scheduleAtFixedRate(() -> { 61 | for (Entry entry : executors.entrySet()) { 62 | if (entry.getValue().isShutdown()) { 63 | if (Minecraft.getMinecraft() != null && Minecraft.getMinecraft().thePlayer != null) { 64 | Minecraft.getMinecraft().thePlayer.addChatMessage(new ChatComponentText( 65 | BazaarNotifier.prefix + EnumChatFormatting.RED + "Something has caused the " + entry 66 | .getKey() 67 | + " scheduler to crash. Please report this to the discord server (/bn discord)")); 68 | 69 | } 70 | executors.put(entry.getKey(), getScheduler(entry.getKey())); 71 | } 72 | } 73 | }, 1, 1, TimeUnit.MINUTES); 74 | } 75 | 76 | public ScheduledExecutorService craftingBankLoop() { 77 | ScheduledExecutorService ex = Executors.newScheduledThreadPool(1); 78 | ex.scheduleAtFixedRate(() -> { 79 | if (BazaarNotifier.activeBazaar) { 80 | try { 81 | CraftingCalculator.getBestEnchantRecipes(); 82 | 83 | // Reset the bank calculator on the new day 84 | // It probably isn't necessary to do this in a scheduler, but here we are. 85 | long currentTime = System.currentTimeMillis(); 86 | Date reset = new Date(currentTime - (currentTime % 86400000)); 87 | if (reset.after(new Date(BazaarNotifier.config.lastLogin))) { 88 | BazaarNotifier.config.lastLogin = System.currentTimeMillis(); 89 | BazaarNotifier.config.bankModule.bazaarDailyAmount = 1E10; 90 | } 91 | } catch (Exception t) { 92 | t.printStackTrace(); 93 | } 94 | } 95 | }, 5, 5, TimeUnit.SECONDS); 96 | return ex; 97 | } 98 | 99 | public ScheduledExecutorService getBazaarData() { 100 | ScheduledExecutorService ex = Executors.newScheduledThreadPool(1); 101 | ex.scheduleAtFixedRate(() -> { 102 | if (BazaarNotifier.activeBazaar) { 103 | try { 104 | BazaarNotifier.bazaarDataRaw = Utils.getBazaarData(); 105 | } catch (Exception t) { 106 | t.printStackTrace(); 107 | } 108 | } 109 | }, 0, 2, TimeUnit.SECONDS); 110 | return ex; 111 | } 112 | 113 | public ScheduledExecutorService suggestionLoop() { 114 | ScheduledExecutorService ex = Executors.newScheduledThreadPool(1); 115 | ex.scheduleAtFixedRate(() -> { 116 | if (BazaarNotifier.activeBazaar) { 117 | try { 118 | SuggestionCalculator.basic(); 119 | } catch (Exception t) { 120 | t.printStackTrace(); 121 | } 122 | } 123 | }, 5, 5, TimeUnit.SECONDS); 124 | return ex; 125 | } 126 | 127 | public ScheduledExecutorService collectionLoop() { 128 | ScheduledExecutorService ex = Executors.newScheduledThreadPool(1); 129 | ex.scheduleAtFixedRate(() -> { 130 | if (BazaarNotifier.activeBazaar) { 131 | try { 132 | CraftingCalculator.getUnlockedRecipes(); 133 | } catch (Exception t) { 134 | t.printStackTrace(); 135 | } 136 | } 137 | }, 5, 5, TimeUnit.MINUTES); 138 | return ex; 139 | } 140 | 141 | 142 | public ScheduledExecutorService notificationLoop() { 143 | ScheduledExecutorService ex = Executors.newScheduledThreadPool(1); 144 | ex.scheduleAtFixedRate(() -> { 145 | if (BazaarNotifier.activeBazaar) { 146 | try { 147 | for (Order order : BazaarNotifier.orders) { 148 | order.updateStatus(); 149 | } 150 | } catch (Exception t) { 151 | t.printStackTrace(); 152 | } 153 | } 154 | }, 0, 2, TimeUnit.SECONDS); 155 | 156 | return ex; 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /src/main/java/dev/meyi/bn/utilities/Utils.java: -------------------------------------------------------------------------------- 1 | package dev.meyi.bn.utilities; 2 | 3 | import com.google.common.collect.BiMap; 4 | import com.google.common.collect.HashBiMap; 5 | import com.google.gson.Gson; 6 | import com.google.gson.JsonElement; 7 | import com.google.gson.JsonObject; 8 | import com.google.gson.JsonSyntaxException; 9 | import com.google.gson.stream.JsonReader; 10 | import dev.meyi.bn.BazaarNotifier; 11 | import dev.meyi.bn.json.resp.BazaarResponse; 12 | import java.io.BufferedReader; 13 | import java.io.File; 14 | import java.io.IOException; 15 | import java.io.InputStreamReader; 16 | import java.nio.charset.StandardCharsets; 17 | import java.nio.file.Files; 18 | import java.nio.file.Paths; 19 | import java.security.KeyManagementException; 20 | import java.security.NoSuchAlgorithmException; 21 | import java.security.cert.CertificateException; 22 | import java.security.cert.X509Certificate; 23 | 24 | import java.util.ArrayList; 25 | import java.util.List; 26 | import java.util.Map; 27 | import java.util.Set; 28 | import net.minecraft.client.Minecraft; 29 | import net.minecraft.item.ItemStack; 30 | import net.minecraft.nbt.NBTTagList; 31 | import org.apache.commons.io.IOUtils; 32 | import org.apache.commons.lang3.StringUtils; 33 | import org.apache.http.HttpResponse; 34 | import org.apache.http.client.methods.HttpGet; 35 | import org.apache.http.impl.client.CloseableHttpClient; 36 | import org.apache.http.impl.client.HttpClientBuilder; 37 | 38 | import javax.net.ssl.SSLContext; 39 | import javax.net.ssl.TrustManager; 40 | import javax.net.ssl.X509TrustManager; 41 | 42 | public class Utils { 43 | private static final TrustManager[] trustAllCerts = new TrustManager[] { 44 | new X509TrustManager() { 45 | @Override 46 | public void checkClientTrusted(java.security.cert.X509Certificate[] x509Certificates, String s) throws CertificateException { 47 | } 48 | 49 | @Override 50 | public void checkServerTrusted(java.security.cert.X509Certificate[] x509Certificates, String s) throws CertificateException { 51 | } 52 | 53 | @Override 54 | public X509Certificate[] getAcceptedIssuers() { 55 | return null; 56 | } 57 | 58 | } 59 | }; 60 | private static String playerUUID = ""; 61 | 62 | public static BazaarResponse getBazaarData() throws IOException { 63 | Gson gson = new Gson(); 64 | CloseableHttpClient client = HttpClientBuilder.create().build(); 65 | HttpGet request = new HttpGet( 66 | "https://api.hypixel.net/v2/skyblock/bazaar"); 67 | HttpResponse response = client.execute(request); 68 | 69 | String result = IOUtils.toString(new BufferedReader 70 | (new InputStreamReader( 71 | response.getEntity().getContent()))); 72 | 73 | client.close(); 74 | 75 | if (isJSONValid(result)) { 76 | return gson.fromJson(result, BazaarResponse.class); 77 | } else { 78 | return new BazaarResponse(false, 0, null); 79 | } 80 | } 81 | 82 | 83 | /** 84 | * Waiting until a proper backend is built out before completing this feature. 85 | * 86 | * @see the 88 | * old code 89 | */ 90 | public static List unlockedRecipes() throws IOException { 91 | Gson gson = new Gson(); 92 | if (BazaarNotifier.config.collectionCheck) { 93 | try (CloseableHttpClient client = HttpClientBuilder.create().build()) { 94 | if (playerUUID.equals("")) { 95 | HttpGet request = new HttpGet( 96 | "https://api.mojang.com/users/profiles/minecraft/" + Minecraft.getMinecraft() 97 | .getSession().getUsername()); // Change this to your username if testing 98 | HttpResponse response = client.execute(request); 99 | 100 | String uuidResponse = IOUtils 101 | .toString( 102 | new BufferedReader(new InputStreamReader(response.getEntity().getContent()))); 103 | 104 | try { 105 | playerUUID = gson.fromJson(uuidResponse, JsonObject.class).get("id").getAsString(); 106 | } catch (JsonSyntaxException e) { 107 | return null; 108 | } 109 | } 110 | } 111 | } 112 | return null; 113 | } 114 | 115 | public static boolean isJSONValid(String jsonInString) { 116 | Gson gson = new Gson(); 117 | try { 118 | gson.fromJson(jsonInString, JsonObject.class); 119 | return true; 120 | } catch (Exception ex) { 121 | return false; 122 | } 123 | } 124 | 125 | public static void updateResources() throws IOException, KeyManagementException, NoSuchAlgorithmException, ClassCastException { 126 | Gson gson = new Gson(); 127 | HttpGet request; 128 | HttpResponse response; 129 | SSLContext sc = SSLContext.getInstance("SSL"); 130 | sc.init(null, trustAllCerts, new java.security.SecureRandom()); 131 | CloseableHttpClient client = HttpClientBuilder.create().setSslcontext(sc).build(); 132 | request = new HttpGet(BazaarNotifier.RESOURCE_LOCATION); 133 | response = client.execute(request); 134 | 135 | JsonReader jsonReader = new JsonReader( 136 | new BufferedReader(new InputStreamReader(response.getEntity().getContent(), StandardCharsets.UTF_8))); 137 | jsonReader.setLenient(true); 138 | try { 139 | BazaarNotifier.resources = gson.fromJson(jsonReader, JsonObject.class); 140 | BazaarNotifier.bazaarConv = jsonToBimap( 141 | BazaarNotifier.resources.getAsJsonObject("bazaarConversions")); 142 | BazaarNotifier.enchantCraftingList = BazaarNotifier.resources 143 | .getAsJsonObject("enchantCraftingList"); 144 | } catch (JsonSyntaxException | ClassCastException e) { //ClassCastException is thrown when GitHub is down 145 | e.printStackTrace(); 146 | } finally { 147 | client.close(); 148 | } 149 | } 150 | 151 | public static BiMap jsonToBimap(JsonObject jsonObject) { 152 | BiMap b = HashBiMap.create(); 153 | Set> entries = jsonObject.entrySet(); 154 | for (Map.Entry entry : entries) { 155 | try { 156 | b.put(entry.getKey(), jsonObject.get(entry.getKey()).getAsString()); 157 | } catch (IllegalArgumentException ignored) { 158 | } 159 | } 160 | return b; 161 | } 162 | 163 | public static void saveResources(File file, JsonObject resources) { 164 | Gson gson = new Gson(); 165 | try { 166 | if (!file.isFile()) { 167 | //noinspection ResultOfMethodCallIgnored 168 | file.createNewFile(); 169 | } 170 | Files.write(Paths.get(file.getAbsolutePath()), 171 | gson.toJson(resources).getBytes(StandardCharsets.UTF_8)); 172 | } catch (IOException e) { 173 | e.printStackTrace(); 174 | } 175 | } 176 | 177 | public static String[] getItemIdFromName(String userInput) { 178 | int threshold = userInput.length() / 3; 179 | String closestConversion = ""; 180 | int minLevenshteinDistance = threshold + 1; 181 | 182 | for (String key : BazaarNotifier.bazaarConv.values()) { 183 | int levenshteinDistance = StringUtils 184 | .getLevenshteinDistance(userInput.toLowerCase(), key.toLowerCase(), threshold); 185 | if (levenshteinDistance != -1) { 186 | if (minLevenshteinDistance > levenshteinDistance) { 187 | minLevenshteinDistance = levenshteinDistance; 188 | closestConversion = key; 189 | if (levenshteinDistance == 0) { 190 | break; 191 | } 192 | } 193 | } 194 | } 195 | 196 | return new String[]{closestConversion, 197 | BazaarNotifier.bazaarConv.inverse().getOrDefault(closestConversion, "")}; 198 | } 199 | 200 | public static List getLoreFromItemStack(ItemStack item) { 201 | NBTTagList lorePreFilter = item.getTagCompound().getCompoundTag("display") 202 | .getTagList("Lore", 8); 203 | 204 | List lore = new ArrayList<>(); 205 | 206 | for (int j = 0; j < lorePreFilter.tagCount(); j++) { 207 | lore.add(net.minecraft.util.StringUtils.stripControlCodes(lorePreFilter.getStringTagAt(j))); 208 | } 209 | 210 | return lore; 211 | } 212 | 213 | public static int getOrderAmountLeft(List lore, int totalAmount) { 214 | int amountLeft; 215 | if (lore.get(3).startsWith("Filled:")) { 216 | if (lore.get(3).split(" ")[2].contains("100%")) { 217 | amountLeft = 0; 218 | } else { 219 | String intToParse = lore.get(3).split(" ")[1].split("/")[0]; 220 | int amountFulfilled; 221 | 222 | if (intToParse.contains("k")) { 223 | amountFulfilled = (int) (Double.parseDouble(intToParse.replace("k", "")) * 1000); 224 | } else { 225 | amountFulfilled = Integer.parseInt(intToParse); 226 | } 227 | 228 | amountLeft = totalAmount - amountFulfilled; 229 | } 230 | } else { 231 | amountLeft = totalAmount; 232 | } 233 | return amountLeft; 234 | } 235 | } 236 | -------------------------------------------------------------------------------- /src/main/resources/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/symt/BazaarNotifier/b4b089d210dac724b50f494cb781427f2366052b/src/main/resources/icon.png -------------------------------------------------------------------------------- /src/main/resources/mcmod.info: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "modid": "BazaarNotifier", 4 | "name": "BazaarNotifier", 5 | "description": "Notifies when your bazaar orders are outdated.", 6 | "version": "${version}", 7 | "mcversion": "${mcversion}", 8 | "url": "https://github.com/symt/BazaarNotifier", 9 | "updateUrl": "https://github.com/symt/BazaarNotifier/releases", 10 | "authorList": ["meyi"], 11 | "credits": "meyi", 12 | "logoFile": "", 13 | "screenshots": [], 14 | "dependencies": [] 15 | } 16 | ] -------------------------------------------------------------------------------- /wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/symt/BazaarNotifier/b4b089d210dac724b50f494cb781427f2366052b/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | --------------------------------------------------------------------------------