├── .github └── workflows │ └── go.yml ├── .gitignore ├── LICENSE ├── MCD_conf.ini ├── Plugin_conf.ini ├── README.md ├── command ├── command.go ├── playerGroup.go └── text.go ├── config └── config.go ├── container └── Container.go ├── go.mod ├── hotPlugins ├── .keep ├── ExampleHotPlugin └── ExampleHotPlugin.go ├── lib ├── log.go ├── parse.go ├── plugin.go └── server.go ├── logs └── .keep ├── parsers ├── BackupParser.go ├── ChatParser.go ├── HereParser.go ├── LoginoutParser.go ├── SDChatParser.go ├── TpsParser.go ├── WarnParser.go ├── defaultParser.go └── parserList.go ├── plugins ├── AutoBackupPlugin.go ├── BackupPlugin │ ├── BackupPlugin.go │ └── copy.go ├── BasePlugin.go ├── ChatPlugin │ ├── ChatPlugin.go │ ├── chatBuf.pb.go │ ├── chatBuf.proto │ ├── webSocketClient.go │ ├── webSocketInterface.go │ └── webSocketServer.go ├── ExampleYinyinPlugin.go ├── HerePlugin.go ├── ImagePlugin.go ├── PluginMap.go ├── SDChatPlugin.go ├── StatsHelperPlugin.go ├── TpsPlugin.go ├── WarnPlugin.go ├── hotPlugin.go ├── pluginList_linux.go └── pluginList_windows.go ├── server ├── plugin.go ├── server.go ├── stdin.go └── stdout.go └── start.go /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | name: Go 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | 11 | build: 12 | name: Build 13 | runs-on: ubuntu-latest 14 | steps: 15 | 16 | - name: Set up Go 1.13 17 | uses: actions/setup-go@v1 18 | with: 19 | go-version: 1.13 20 | id: go 21 | 22 | - name: Check out code into the Go module directory 23 | uses: actions/checkout@v2 24 | 25 | - name: Get dependencies 26 | run: | 27 | go get -v -t -d ./... 28 | if [ -f Gopkg.toml ]; then 29 | curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh 30 | dep ensure 31 | fi 32 | 33 | - name: Build 34 | run: go build -v . 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, build with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | #minecraft 15 | minecraft* 16 | logs/* 17 | !.keep 18 | back-up/* 19 | start 20 | *.rar 21 | *.log 22 | test.go 23 | go.sum -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | 635 | Copyright (C) 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | Copyright (C) 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | -------------------------------------------------------------------------------- /MCD_conf.ini: -------------------------------------------------------------------------------- 1 | # 运行方式 2 | ; run_environment = develop //打印调试日志 3 | run_environment = product 4 | 5 | # MCD参数 6 | [MCDeamon] 7 | 8 | # 服务器启动文件名称 9 | server_name = server.jar ;fabric-server-launch.jar 10 | 11 | # 服务器启动文件, 完整路径为 server_path + '/' + server_name 12 | server_path = minecraft ;也可以为相对路径,eg: .. 13 | 14 | # authlib-injector.jar, 需为完整绝对路径, eg: /home/mc/authlib-injector.jar 15 | agent = ; 留空则不启用 16 | 17 | # authlib-injector 所使用的皮肤站链接,eg: https://littleskin.cn/api/yggdrasil 18 | yggdrasil-url = 19 | 20 | # jvm最小堆内存 21 | Xms = 1024M 22 | 23 | # jvm最大堆内存 24 | Xmx = 1024M 25 | 26 | # 是否使用图形界面(用于windows系统以及带图形界面的linux系统) 27 | gui = false 28 | 29 | # 插件最大并发数 30 | maxRunPlugins = 10 31 | 32 | #插件参数 ,也是配置命令对应的插件, 格式为: 命令 = 插件包的名称 33 | [plugins] 34 | !!repeat = ExampleHotPlugin -------------------------------------------------------------------------------- /Plugin_conf.ini: -------------------------------------------------------------------------------- 1 | #插件配置文件 2 | [SDChat] 3 | #图灵机器人appid 4 | appid = XXXX 5 | 6 | #多服务器聊天配置 7 | #本地开启聊天服务器 8 | [LinkChat] 9 | server_port = 22444 #服务端开启端口 10 | server_sub_url = #连接地址子路由,不懂可以不用改 11 | server_name = 主机 #本机服务器对外的名称 12 | server_name_color = white #服务器名字 13 | server_player_color = white #玩家颜色 14 | server_content_color = white #发言颜色 15 | is_start = 1 #是否开启聊天服务器(需要服务器在公网上) 16 | 17 | #连接进来的白名单 18 | [LinkChat.whitelist] 19 | # whitelist(固定名称) = 服务器名称 20 | whitelist = 副机 21 | whitelist = 主机2副机1 22 | 23 | #要连接的聊天服务器, 不能重名! 24 | [LinkChat.server] 25 | #serverhost = 要连接的聊天服务器的ip 26 | ; serverhost = 127.0.0.1:22444 27 | 28 | # 自动备份, 仅限Linux 29 | [AutoBackup] 30 | # workdir, 一般与 MCD_conf.ini 中的 server_path 一致, 留空关闭自动备份 31 | workdir = minecraft ; 切勿出现workdir包含备份文件夹的情况!否则会递归备份占满磁盘,报错11 32 | 33 | # 统计信息助手 34 | [StatsHelper] 35 | # <记分项> = <显示名称> 36 | # e.g. deathcount = 死亡榜 37 | # 以上例子需提前在服务器里手动运行 38 | # /scoreboard objectives add deathcount minecraft.custom:minecraft.deaths "死亡榜" 39 | # 才有效 40 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MCDaemon-go 2 | [![Go Report Card](https://goreportcard.com/badge/github.com/Dark-Night-Base/MCDaemon-go)](https://goreportcard.com/report/github.com/Dark-Night-Base/MCDaemon-go) 3 | ![Go](https://github.com/Dark-Night-Base/MCDaemon-go/workflows/Go/badge.svg) 4 | 5 | ## 用golang实现的Minecraft进程管理程序 6 | 7 | 在windows10以及centos7中运行成功 8 | 9 | ----- 10 | 11 | ## 开始使用 12 | - 下载最新的[release](https://github.com/TISUnion/MCDaemon-go/releases)(beta版不提供) 13 | ### 快速开始 14 | - 修改配置文件MCD_conf.ini 15 | - 解压最新版MCDaemon,进入并创建一个minecraft文件夹 16 | - 将下载MC服务端放入创建的minecraft文件夹内,重命名为server.jar 17 | - 运行start(linux/unix)或者start.exe(windows) 18 | - 在游戏中输入!!server help命令来查看命令帮助 19 | ----- 20 | ## 配置文件 21 | ### MCD_conf.ini 22 | - run_environment:运行方式默认为develop开发者模式(暂时对实际运行没有任何影响) 23 | - server_name&server_path:决定了服务器启动后生成文件的位置,服务器文件的logs, world等文件都会生成在填写的server_path路径中,默认为`minecraft`。server_name则是要运行服务端文件名,默认为`server.jar` 24 | - Xms: jvm运行的最小堆内存 25 | - Xmx:jvm最大堆内存 26 | - gui:是否使用图形界面,默认为true。`注意`,只有带有图形界面的操作系统才会起作用。 27 | - maxRunPlugins:插件最大并发数,默认为`10`,所有插件都是异步运行的,输出是同步输出到服务端。 28 | - plugins: 该域下填写的都是热插件参数,键为调用插件的命令,值为插件对应的二进制文件名称,`注意`, 二进制文件都在hotPlugins文件夹下 29 | ----- 30 | 31 | ## MCDaemon的插件收录 32 | 33 | > 插件列表 34 | 35 | - 基础核心插件:BasePlugin 36 | - 栗子插件:ExampleYinyinPlugin 37 | - TPS查看插件:TpsPlugin 38 | - 沙雕聊天机器人插件:SDChatPlugin 39 | - 备份插件:BackupPlugin 40 | - 镜像管理插件:ImagePlugin 41 | - 加载热插件:hotPlugin 42 | - 多服务器聊天插件:ChatPlugin 43 | - 自动冗余增量备份插件:AutoBackupPlugin 44 | - 坐标广播插件:HerePlugin 45 | - 统计信息助手插件:StatsHelperPlugin 46 | - 警告信息转发插件:WarnPlugin 47 | 48 | ----- 49 | 50 | ## 插件编写 51 | 52 | - ### 热插件 53 | - 将热插件的执行文件放入hotPlugins文件夹中 54 | - 在MCD_conf.ini的[plugins]域中注册热插件 55 | - 通过如下代码获取插件的命令的参数: 56 | ```golang 57 | args := os.Args 58 | args = args[1:] 59 | ``` 60 | - 执行文件的输出:第一个参数固定为回调函数名称,参数之间用空格区别,提供三个函数,三个函数分别为: 61 | - say: 对全服玩家说话,第二个参数为说的字符串 62 | - tell: 对某一玩家说话,第二个参数为玩家名称, 第二个参数为说的字符串 63 | - Execute: 执行某一命令,第二个参数为命令字符串 64 | 65 | 输出例子: 66 | ```text 67 | say hello everyone //对所有人说hello everyone 68 | tell Alice hello //对Alice说hello 69 | Execute debug start //执行tps命令 70 | ``` 71 | - ### 冷插件 72 | 所有冷插件都在plugins文件夹中,包含一个栗子和所有插件。 73 | 74 | 所有插件需要实现lib包里的Plugin接口,Handle方法为插件的调用方法,当输入命令触发插件时,就会运行该方法。 75 | 例子: 76 | ```golang 77 | func (hp *Yinyin) Handle(c *command.Command, s lib.Server) { 78 | s.Say(fmt.Sprintf("%s对所有人说:嘤嘤嘤!", c.Player)) //调用lib.Server接口的say方法对全服人说话 79 | } 80 | ``` 81 | 传入的参数为命令对象(command.Command)和被调用的服务器的接口(lib.Server),命令对象中有解释器传过来的玩家名称,调用命令以及调用命令参数等属性。服务器接口则提供插件开发需要所有接口。服务器接口的方法会在下面详细讲述。 82 | 83 | `注意` 开发完成的插件需要到插件列表中注册,在plugins下的 84 | pluginList.go中添加如下代码: 85 | ```golang 86 | .... 87 | //注册冷插件 88 | PluginsList.RegisterPlugin("命令", &PluginName{}) //注册XXX插件 89 | .... 90 | ``` 91 | 这里的命令就是游戏中输入的命令,默认是!或者!!作为前缀,如果想修改可以直接修改语法解释器或者自定义一个新的语法解释器 92 | 93 | 如果想要获取插件配置文件,可以使用config.GetPluginCfg()方法,该方法返回一个*ini.File类型,具体用法可以参考[go-ini](https://github.com/go-ini/ini)这个第三方库,插件配置文件是Plugin_conf.ini,配置参数写在一个域内。 94 | - ### lib.Server接口方法 95 | - Say 对全服发送消息 96 | - Tell 对某一个玩家发送消息,第二个参数玩家昵称,第三个为发送的消息字符串 97 | - Execute 执行一个原版MC命令 98 | - Close 关闭当前服务器实例对应服务器 99 | - Restart 重启当前服务器实例对应服务器 100 | - Start 开启当前服务器实例里配置的服务器 101 | - ReloadConf 重新读取配置文件,加入热插件后需要重新读取配置文件才能生效 102 | - GetPluginList 获取可使用插件命令列表 103 | - GetDisablePluginList 获取被禁用插件列表 104 | - GetParserList 获取解释器插件列表 105 | - RunPlugin 执行插件传入一个command.Command对象,即可在一个插件中调用其他插件 106 | - RunUniquePlugin 传入一个函数,等待所有正在运行的插件运行完毕后堵塞住插件运行池,然后运行传入的函数。 107 | - WriteLog 写入日志,第一个参数为消息等级,第二个为写入日志的消息,日志可以在logs文件中查看,日志登级分为:debug、info、warn、error和fatal。 108 | - Clone 克隆出一个server.Server实例用于承载镜像 109 | - GetPort 开启服务器的端口,配置文件中为默认端口,镜像则会随机一个可用的端口。 110 | - Getinfo 获取镜像名称和端口 111 | - CloseInContainer 在容器中关闭镜像,与Close不同的是Close用单服务器,无法注销容器中对应的服务器数据 112 | 113 | 以上方法除了Close都适用于单服务器和多服务器运行,后面会提到多服务器镜像的使用 114 | 115 | ----- 116 | 117 | ## 高级组件 118 | - ### 打印调试信息 119 | 编写插件时,总是需要查看一些调试信息,在lib包下的log中提供了WriteDevelopLog和WriteRuntimeLog两个函数打印相关日志, 120 | WriteDevelopLog是写入开发调试信息在logs/develop.log中,写入还需要在MCD_conf.ini中把运行环境配置run_environment改成develop, 121 | 如果觉得看日志很繁琐,还可以修改lib包下log中的printToCmd来使信息显示在命令行上。而WriteRuntimeLog是运行时日志,按日来划分文件。 122 | - ### 自定义语法解释器 123 | 所有语法解释器都在parsers中,所有解释器需要实现lib中的Parser接口。 124 | 125 | Parsing方法会传入一个字符串,是从服务器接受的一条信息,例如玩家发言的结构为: 126 | 127 | [时间] [Server thread/INFO]: 玩家名 说的话 128 | 129 | 以及执行命令完服务器返回的特殊结构的数据(一般都为json格式),所以我们需要对该字符串做强正则,因为程序会对该条信息运行所有的语法解释器,需保证该解释器不会和其他解释器捕获同一条消息,当然如果想要两个解释器捕获同一条信息也是可以的。 130 | 131 | 和插件类似,写完的解释器需要在parsers文件夹下的parserList.go中加入实现的解释器对象。 132 | 133 | 例子: 134 | ```golang 135 | .... 136 | return []lib.Parser{ 137 | .... 138 | &XXXParser{}, 139 | .... 140 | } 141 | ``` 142 | - ### 多服务器镜像 143 | 144 | 镜像主要基于备份插件,如果要使用多镜像,需要了解容器,容器代码在container包下的Container.go中,下面介绍如何使用容器: 145 | - container.GetInstance() 获取全局容器实例,这是一个单例,容器原则上整个程序运行时只允许有一个。开发者也可以自己在插件中创建容器实例,但是并不推荐。 146 | - Add 容器实例的方法,添加一个服务器镜像。第一个参数为镜像名称,用于区分镜像,第二各参数为镜像所在的目录,第三个参数为lib.server接口,用来Clone一个Server实例来承载服务器。 147 | - Del 关闭一个服务器镜像,传入镜像名称即可 148 | - GetRuntimeServer 获取所有正在运行镜像的lib.Server接口,可用来说像所有镜像服务器发送消息甚至运行插件。 149 | - IsRuntime 传入一个镜像名称,查询改镜像是否启动 150 | 151 | - ### 玩家组 152 | 在对玩家进行分类分组时,由于每个插件在同一个服务器中的实例就一个。所以,在单服务器中可以自建玩家组区分玩家,但是如果想对全部镜像进行玩家分组,则需要使用提供玩家组组件。 153 | 154 | 玩家组代码在command包的playerGroup.go里,使用方法如下: 155 | 156 | - command.Group 全局玩家组实例,在该实例下有一系列创建玩家组,在组中添加删除玩家的方法 157 | - AddPlayer 玩家组实例方法, 将玩家加入到指定用户组中, 第一个参数为用户组名称,第二个参数为玩家名称 158 | - DelPlayer 将玩家在指定用户组中删除, 第一个参数为用户组名称,第二个参数为玩家名称 159 | - HasPlayer 查询玩家在指定用户组中是否存在, 第一个参数为用户组名称,第二个参数为玩家名称 160 | - GetPlayer 获取整个玩家组数组 161 | - ### 色彩缤纷的消息回复 162 | 在使用lib.Server接口的Say以及Tell方法时, 不仅仅可以传入字符串, 还可能传入command.Text类型的数据, command.Text对象只有2个参数, 一个是消息字符串, 一个消息的颜色, 可以传入多个Text来生成一句各种颜色的消息。 163 | 164 | 例子: 165 | ```golang 166 | s.Say(command.Text{"hello", "green"}, command.Text{"everyone", "red"}) 167 | 168 | s.Tell(playerName, command.Text{"hello", "green"}, command.Text{playerName, "red"}) 169 | 170 | //也可以传入数组 171 | s.Say([]command.Text{command.Text{"hello", "green"}, command.Text{"everyone", "red"}}) 172 | 173 | s.Tell(playerName, []command.Text{command.Text{"hello", "green"}, command.Text{playerName, "red"}}) 174 | ``` 175 | -------------------------------------------------------------------------------- /command/command.go: -------------------------------------------------------------------------------- 1 | package command 2 | 3 | type Command struct { 4 | Player string //玩家名 5 | PluginName string //插件名 6 | Argv []string //参数 7 | Cmd string //命令名 8 | } 9 | -------------------------------------------------------------------------------- /command/playerGroup.go: -------------------------------------------------------------------------------- 1 | package command 2 | 3 | import ( 4 | "sync" 5 | ) 6 | 7 | var Group *PlayerGroup 8 | 9 | type PlayerGroup struct { 10 | players map[string][]string 11 | lock sync.Mutex 12 | } 13 | 14 | func init() { 15 | Group = &PlayerGroup{} 16 | Group.players = make(map[string][]string) 17 | } 18 | 19 | //向组内添加玩家 20 | func (pg *PlayerGroup) AddPlayer(groupname string, player string) { 21 | pg.lock.Lock() 22 | defer pg.lock.Unlock() 23 | pg.players[groupname] = append(pg.players[groupname], player) 24 | } 25 | 26 | //删除组内指定玩家 27 | func (pg *PlayerGroup) DelPlayer(groupname string, player string) { 28 | pg.lock.Lock() 29 | defer pg.lock.Unlock() 30 | //如果在组内,存在才删除 31 | if pg._hasPlayer(groupname, player, false) { 32 | var index int 33 | for k, val := range pg.players[groupname] { 34 | if val == player { 35 | index = k 36 | break 37 | } 38 | } 39 | //去除查询到的玩家 40 | pg.players[groupname] = append(pg.players[groupname][0:index], pg.players[groupname][index+1:]...) 41 | } 42 | } 43 | 44 | //查询组内是否有指定玩家 45 | func (pg *PlayerGroup) HasPlayer(groupname string, player string) bool { 46 | return pg._hasPlayer(groupname, player, true) 47 | } 48 | 49 | func (pg *PlayerGroup) GetPlayer() map[string][]string { 50 | pg.lock.Lock() 51 | defer pg.lock.Unlock() 52 | return pg.players 53 | } 54 | 55 | func (pg *PlayerGroup) _hasPlayer(groupname string, player string, isLock bool) bool { 56 | if isLock { 57 | pg.lock.Lock() 58 | defer pg.lock.Unlock() 59 | } 60 | if len(pg.players) > 0 && pg.players[groupname] != nil { 61 | for _, val := range pg.players[groupname] { 62 | if val == player { 63 | return true 64 | } 65 | } 66 | } 67 | return false 68 | } 69 | -------------------------------------------------------------------------------- /command/text.go: -------------------------------------------------------------------------------- 1 | package command 2 | 3 | import "encoding/json" 4 | 5 | type Text struct { 6 | Text string `json:"text"` 7 | Color string `json:"color"` 8 | } 9 | 10 | func JsonEncode(argv ...interface{}) (string, bool) { 11 | var texts []Text 12 | for _, v := range argv { 13 | switch t := v.(type) { 14 | case []Text: 15 | return _jsonEncode(t...), true 16 | case Text: 17 | texts = append(texts, t) 18 | default: 19 | return "", false 20 | } 21 | } 22 | return _jsonEncode(texts...), true 23 | } 24 | 25 | func _jsonEncode(argv ...Text) string { 26 | var texts []Text 27 | for _, v := range argv { 28 | texts = append(texts, v) 29 | } 30 | res, _ := json.Marshal(texts) 31 | return string(res) 32 | } 33 | -------------------------------------------------------------------------------- /config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/go-ini/ini" 8 | ) 9 | 10 | //配置变量 11 | var ( 12 | Cfg *ini.File 13 | err error 14 | plugins map[string]string 15 | PluginCfg *ini.File 16 | ) 17 | 18 | func init() { 19 | //加载配置文件 20 | Cfg, err = ini.Load("MCD_conf.ini") 21 | if err != nil { 22 | fmt.Printf("读取配置文件失败: %v", err) 23 | os.Exit(1) 24 | } 25 | } 26 | 27 | //获取服务器启动配置 28 | func GetStartConfig() []string { 29 | //读取配置 30 | Section := Cfg.Section("MCDeamon") 31 | serverName := Section.Key("server_name").String() 32 | // serverPath := Section.Key("server_path").String() 33 | //设置默认值 34 | xms := Section.Key("Xms").Validate(func(in string) string { 35 | if len(in) == 0 { 36 | return "-Xms1024M" 37 | } 38 | return fmt.Sprint("-Xms", in) 39 | }) 40 | xmx := Section.Key("Xmx").Validate(func(in string) string { 41 | if len(in) == 0 { 42 | return "-Xmx1024M" 43 | } 44 | return fmt.Sprint("-Xmx", in) 45 | }) 46 | gui := Section.Key("gui").Validate(func(in string) string { 47 | if len(in) == 0 { 48 | return "false" 49 | } 50 | return in 51 | }) 52 | agent := Section.Key("agent").Validate(func(in string) string { 53 | url := Section.Key("yggdrasil-url").Value() 54 | if len(in) == 0 { 55 | return "" 56 | } 57 | if len(url) == 0 { 58 | fmt.Println("未指定 yggdrasil-url!不会启用 -javaagent 参数") 59 | return "" 60 | } 61 | return fmt.Sprint("-javaagent:", in, "=", url) 62 | }) 63 | var result []string 64 | if len(agent) == 0 { 65 | result = []string{ 66 | xmx, 67 | xms, 68 | "-jar", 69 | serverName, 70 | } 71 | } else { 72 | result = []string{ 73 | xmx, 74 | xms, 75 | agent, 76 | "-jar", 77 | serverName, 78 | } 79 | } 80 | if gui != "true" { 81 | result = append(result, "nogui") 82 | } 83 | return result 84 | } 85 | 86 | //获取插件配置 87 | func GetPlugins(is_rebuild bool) map[string]string { 88 | if is_rebuild { 89 | Cfg, err = ini.Load("MCD_conf.ini") 90 | if err != nil { 91 | fmt.Printf("读取配置文件失败: %v", err) 92 | os.Exit(1) 93 | } 94 | //重置配置文件 95 | plugins = nil 96 | } 97 | if plugins == nil { 98 | plugins = make(map[string]string) 99 | keys := Cfg.Section("plugins").KeyStrings() 100 | for _, val := range keys { 101 | plugins[val] = Cfg.Section("plugins").Key(val).String() 102 | } 103 | } 104 | return plugins 105 | } 106 | 107 | //根据命令获取插件 108 | func GetPluginName(cmd string) string { 109 | pluins := GetPlugins(false) 110 | return pluins[cmd] 111 | } 112 | 113 | //获取插件配置文件对象 114 | func GetPluginCfg(is_rebuild bool) *ini.File { 115 | //加载配置文件 116 | if PluginCfg == nil || is_rebuild { 117 | PluginCfg, err = ini.ShadowLoad("Plugin_conf.ini") 118 | if err != nil { 119 | fmt.Printf("读取插件配置文件失败: %v", err) 120 | } 121 | } 122 | return PluginCfg 123 | } 124 | 125 | func SetEula() { 126 | path := fmt.Sprintf("%s/eula.txt", Cfg.Section("MCDeamon").Key("server_path").String()) 127 | eulaCfg, eulaerr := ini.Load(path) 128 | //不存在eula.txt 129 | if eulaerr != nil { 130 | eulaCfg = ini.Empty() 131 | eulaCfg.Section("").NewKey("eula", "true") 132 | _ = eulaCfg.SaveTo(path) 133 | } 134 | //如果为false 135 | if eulaCfg.Section("").Key("eula").String() == "false" { 136 | eulaCfg.Section("").NewKey("eula", "true") 137 | _ = eulaCfg.SaveTo(path) 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /container/Container.go: -------------------------------------------------------------------------------- 1 | package container 2 | 3 | import ( 4 | "MCDaemon-go/config" 5 | "MCDaemon-go/lib" 6 | "sync" 7 | ) 8 | 9 | //单例模式 10 | var ( 11 | ContainerInstance *Container 12 | once sync.Once 13 | ) 14 | 15 | //获取单例实例 16 | func GetInstance() *Container { 17 | once.Do(func() { 18 | ContainerInstance = &Container{} 19 | ContainerInstance.Init() 20 | }) 21 | return ContainerInstance 22 | } 23 | 24 | type Container struct { 25 | Servers map[string]lib.Server //所有已启动的服务器 26 | lock sync.Mutex //同步锁 27 | Group sync.WaitGroup //协程组同步 28 | } 29 | 30 | func (c *Container) Init() { 31 | c.Servers = make(map[string]lib.Server) 32 | } 33 | 34 | func (c *Container) Add(name string, workDir string, svr lib.Server) { 35 | c.lock.Lock() 36 | defer c.lock.Unlock() 37 | c.Servers[name] = svr 38 | //读取配置 39 | argv := config.GetStartConfig() 40 | c.Group.Add(1) 41 | go svr.Start(name, argv, workDir) 42 | } 43 | 44 | func (c *Container) Del(name string) { 45 | c.lock.Lock() 46 | defer c.lock.Unlock() 47 | if _, ok := c.Servers[name]; ok { 48 | deleteServer := c.Servers[name] 49 | deleteServer.Close() 50 | delete(c.Servers, name) 51 | } 52 | } 53 | 54 | func (c *Container) GetRuntimeServer() []string { 55 | c.lock.Lock() 56 | defer c.lock.Unlock() 57 | var res []string 58 | for k, _ := range c.Servers { 59 | res = append(res, k) 60 | } 61 | return res 62 | } 63 | 64 | func (c *Container) IsRuntime(name string) bool { 65 | c.lock.Lock() 66 | defer c.lock.Unlock() 67 | if _, ok := c.Servers[name]; ok { 68 | return true 69 | } 70 | return false 71 | } 72 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module MCDaemon-go 2 | 3 | go 1.12 4 | 5 | require ( 6 | github.com/go-ini/ini v1.42.0 7 | github.com/golang/protobuf v1.3.1 8 | github.com/sirupsen/logrus v1.4.2 9 | github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a // indirect 10 | github.com/tidwall/gjson v1.2.1 11 | github.com/tidwall/match v1.0.1 // indirect 12 | github.com/tidwall/pretty v1.0.0 // indirect 13 | github.com/uudashr/gopkgs/v2 v2.1.2 // indirect 14 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859 15 | golang.org/x/text v0.3.2 16 | golang.org/x/tools/gopls v0.3.2 // indirect 17 | gopkg.in/ini.v1 v1.42.0 // indirect 18 | ) 19 | -------------------------------------------------------------------------------- /hotPlugins/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TISUnion/MCDaemon-go/703edc0ca24700f7977ad398e7a571407e0478fc/hotPlugins/.keep -------------------------------------------------------------------------------- /hotPlugins/ExampleHotPlugin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TISUnion/MCDaemon-go/703edc0ca24700f7977ad398e7a571407e0478fc/hotPlugins/ExampleHotPlugin -------------------------------------------------------------------------------- /hotPlugins/ExampleHotPlugin.go: -------------------------------------------------------------------------------- 1 | /** 2 | * 热插件栗子 3 | * 这是一个实现复读姬的栗子 4 | */ 5 | package main 6 | 7 | import ( 8 | "fmt" 9 | "os" 10 | ) 11 | 12 | func main() { 13 | //获取指令参数 14 | //注意 args 为数组,第0个参数是命令本身,是不需要的 15 | args := os.Args 16 | args = args[1:] 17 | //返回调用MCD的信息 18 | //注意,不能换行;要做没有传入参数的判断 19 | if len(args) != 0 { 20 | fmt.Print("say ", args[0]) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /lib/log.go: -------------------------------------------------------------------------------- 1 | package lib 2 | 3 | import ( 4 | "MCDaemon-go/config" 5 | "fmt" 6 | "os" 7 | "time" 8 | 9 | "github.com/sirupsen/logrus" 10 | ) 11 | 12 | var ( 13 | devlog *logrus.Logger 14 | commonlog *logrus.Logger 15 | isdevelop bool 16 | printToCmd bool 17 | ) 18 | 19 | func init() { 20 | devlog = logrus.New() 21 | commonlog = logrus.New() 22 | devlog.SetLevel(logrus.DebugLevel) 23 | commonlog.SetLevel(logrus.DebugLevel) 24 | if config.Cfg.Section("").Key("run_environment").String() == "develop" { 25 | isdevelop = true 26 | } else { 27 | isdevelop = false 28 | } 29 | //开发时调试使用,是否显示在命令行中 30 | printToCmd = false 31 | } 32 | 33 | //写入开发日志 34 | func WriteDevelopLog(level string, msg string) { 35 | //如果不是开发者模式,则不写入日志 36 | if !isdevelop { 37 | return 38 | } 39 | if printToCmd { 40 | fmt.Println(msg) 41 | } 42 | logFile, err := os.OpenFile("logs/develop.log", os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0777) 43 | defer logFile.Close() 44 | if err != nil { 45 | fmt.Println("日志写入系统发生错误! 因为", err) 46 | } 47 | devlog.SetOutput(logFile) 48 | switch level { 49 | case "debug": 50 | devlog.Debug(msg) 51 | case "info": 52 | devlog.Info(msg) 53 | case "warn": 54 | devlog.Warn(msg) 55 | case "error": 56 | devlog.Error(msg) 57 | case "fatal": 58 | devlog.Fatal(msg) 59 | } 60 | } 61 | 62 | //写入运行日志 63 | func WriteRuntimeLog(level string, msg string, serverName string) { 64 | logFile, err := os.OpenFile(fmt.Sprintf("logs/%s_%s.log", serverName, time.Now().Format("2006-01-02")), os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0777) 65 | defer logFile.Close() 66 | if err != nil { 67 | fmt.Println("日志写入系统发生错误! 因为", err) 68 | } 69 | commonlog.SetOutput(logFile) 70 | switch level { 71 | case "debug": 72 | commonlog.Debug(msg) 73 | case "info": 74 | commonlog.Info(msg) 75 | case "warn": 76 | commonlog.Warn(msg) 77 | case "error": 78 | commonlog.Error(msg) 79 | case "fatal": 80 | commonlog.Fatal(msg) 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /lib/parse.go: -------------------------------------------------------------------------------- 1 | package lib 2 | 3 | import "MCDaemon-go/command" 4 | 5 | type Parser interface { 6 | Parsing(string) (*command.Command, bool) 7 | } 8 | -------------------------------------------------------------------------------- /lib/plugin.go: -------------------------------------------------------------------------------- 1 | package lib 2 | 3 | import "MCDaemon-go/command" 4 | 5 | type Plugin interface { 6 | Handle(*command.Command, Server) 7 | Init(Server) 8 | Close() 9 | } 10 | -------------------------------------------------------------------------------- /lib/server.go: -------------------------------------------------------------------------------- 1 | package lib 2 | 3 | import ( 4 | "MCDaemon-go/command" 5 | ) 6 | 7 | type Server interface { 8 | Say(...interface{}) //在服务器中全局说话 9 | Tell(string, ...interface{}) //对某人私聊 10 | Execute(string) //执行mc原生命令 11 | Close() //关闭服务器(适用于单服务器) 12 | CloseInContainer() //在容器中关闭服务器 13 | Restart() //重启 14 | Start(string, []string, string) //开启一个服务器实例 15 | Getinfo() string //获取服务器基本信息 16 | Clone() Server //克隆一个服务器实例 17 | GetPort() string //获取服务器端口 18 | ReloadConf() //重新获取配置 19 | RunPlugin(*command.Command) //运行一个插件命令 20 | RunUniquePlugin(func()) //在所有命令完成后执行 21 | WriteLog(level string, msg string) //写入日志 22 | GetPluginList() map[string]Plugin //获取可用插件列表 23 | GetDisablePluginList() map[string]Plugin //获取不可用插件列表 24 | GetParserList() []Parser //获取语法解释器列表 25 | GetName() string //获取服务器名称 26 | } 27 | -------------------------------------------------------------------------------- /logs/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TISUnion/MCDaemon-go/703edc0ca24700f7977ad398e7a571407e0478fc/logs/.keep -------------------------------------------------------------------------------- /parsers/BackupParser.go: -------------------------------------------------------------------------------- 1 | package parser 2 | 3 | import ( 4 | "MCDaemon-go/command" 5 | "regexp" 6 | ) 7 | 8 | type BackupParser struct{} 9 | 10 | func (p *BackupParser) Parsing(word string) (*command.Command, bool) { 11 | re := regexp.MustCompile(`\[\d+:\d+:\d+\]\s+\[Server thread/INFO\]:\s+Saved the game`) 12 | match := re.FindStringSubmatch(word) 13 | if len(match) != 0 { 14 | _commond := &command.Command{ 15 | Cmd: "!!backup", 16 | Argv: []string{"saved"}, 17 | } 18 | return _commond, true 19 | } 20 | return nil, false 21 | } 22 | -------------------------------------------------------------------------------- /parsers/ChatParser.go: -------------------------------------------------------------------------------- 1 | package parser 2 | 3 | import ( 4 | "MCDaemon-go/command" 5 | "regexp" 6 | ) 7 | 8 | type ChatParser struct { 9 | startChatPlayer map[string]bool 10 | } 11 | 12 | func (sp *ChatParser) Parsing(word string) (*command.Command, bool) { 13 | if sp.startChatPlayer == nil { 14 | sp.startChatPlayer = make(map[string]bool) 15 | } 16 | //匹配玩家说的话 17 | re := regexp.MustCompile(`\[\d+:\d+:\d+\]\s+\[Server thread/INFO\]:\s+<(?P.+)>\s+(?P.+)\s*`) 18 | match := re.FindStringSubmatch(word) 19 | groupNames := re.SubexpNames() 20 | result := make(map[string]string) 21 | 22 | //是否是玩家说的话 23 | if len(match) != 0 { 24 | // 转换为map 25 | for i, name := range groupNames { 26 | result[name] = match[i] 27 | } 28 | //如果已开启聊天模式(全局聊天模式) 29 | if command.Group.HasPlayer("ServersChat", result["player"]) { 30 | return &command.Command{ 31 | Player: result["player"], 32 | Argv: []string{"chat_xxx_say", result["word"]}, 33 | Cmd: "!!Chat", 34 | }, true 35 | } 36 | } 37 | return nil, false 38 | } 39 | -------------------------------------------------------------------------------- /parsers/HereParser.go: -------------------------------------------------------------------------------- 1 | package parser 2 | 3 | import ( 4 | "MCDaemon-go/command" 5 | "regexp" 6 | ) 7 | 8 | type HereParser struct{} 9 | 10 | func (p *HereParser) Parsing(word string) (*command.Command, bool) { 11 | //re := regexp.MustCompile(`\[\d+:\d+:\d+\]\s+\[Server thread/INFO\]:\s(\S*)\shas the following entity data`) 12 | //player := re.FindStringSubmatch(word) 13 | //if len(player) == 2 { 14 | re := regexp.MustCompile(`Dimension:\s(\d)`) 15 | dimension := re.FindStringSubmatch(word) 16 | if len(dimension) == 2 { 17 | re = regexp.MustCompile(`Pos:\s\[([-0-9]*).[-0-9]*d,\s([-0-9]*).[-0-9]*d,\s([-0-9]*).[-0-9]*d\]`) 18 | pos := re.FindStringSubmatch(word) 19 | if len(pos) == 4 { 20 | _commond := &command.Command{ 21 | Cmd: "!!here", 22 | Argv: []string{"res", dimension[1], pos[1], pos[2], pos[3]}, 23 | } 24 | return _commond, true 25 | } 26 | } 27 | //} 28 | return nil, false 29 | } 30 | -------------------------------------------------------------------------------- /parsers/LoginoutParser.go: -------------------------------------------------------------------------------- 1 | package parser 2 | 3 | import ( 4 | "MCDaemon-go/command" 5 | "regexp" 6 | ) 7 | 8 | type LoginoutParser struct{} 9 | 10 | func (p *LoginoutParser) Parsing(word string) (*command.Command, bool) { 11 | re := regexp.MustCompile(`\[\d+:\d+:\d+\]\s+\[Server thread/INFO\]:\s(\S*)\sleft the game`) 12 | match := re.FindStringSubmatch(word) 13 | if len(match) == 2 { 14 | _commond := &command.Command{ 15 | Cmd: "!!autobk", 16 | Argv: []string{"save", match[1]}, 17 | } 18 | return _commond, true 19 | } 20 | 21 | // re = regexp.MustCompile(`\[\d+:\d+:\d+\]\s+\[Server thread/INFO\]:\s(\S*)\sjoined the game`) 22 | // match = re.FindStringSubmatch(word) 23 | // if len(match) == 2 { 24 | // _commond := &command.Command{ 25 | // Cmd: "!!autobk", 26 | // Argv: []string{"save"}, 27 | // } 28 | // return _commond, true 29 | // } 30 | 31 | return nil, false 32 | } 33 | -------------------------------------------------------------------------------- /parsers/SDChatParser.go: -------------------------------------------------------------------------------- 1 | package parser 2 | 3 | import ( 4 | "MCDaemon-go/command" 5 | "regexp" 6 | ) 7 | 8 | type SDChatParser struct { 9 | startChatPlayer map[string]bool 10 | } 11 | 12 | func (sp *SDChatParser) Parsing(word string) (*command.Command, bool) { 13 | if sp.startChatPlayer == nil { 14 | sp.startChatPlayer = make(map[string]bool) 15 | } 16 | //匹配玩家说的话 17 | re := regexp.MustCompile(`\[\d+:\d+:\d+\]\s+\[Server thread/INFO\]:\s+<(?P.+)>\s+(?P.+)\s*`) 18 | match := re.FindStringSubmatch(word) 19 | groupNames := re.SubexpNames() 20 | result := make(map[string]string) 21 | 22 | //是否是玩家说的话 23 | if len(match) != 0 { 24 | // 转换为map 25 | for i, name := range groupNames { 26 | result[name] = match[i] 27 | } 28 | //如果已开启聊天模式(私聊模式) 29 | if command.Group.HasPlayer("SDChat", result["player"]) { 30 | return &command.Command{ 31 | Player: result["player"], 32 | Argv: []string{"say", result["word"]}, 33 | Cmd: "!!SDChat", 34 | }, true 35 | } 36 | //如果已开启聊天模式(全局聊天模式) 37 | if command.Group.HasPlayer("SDChat-all", result["player"]) { 38 | return &command.Command{ 39 | Player: result["player"], 40 | Argv: []string{"say-all", result["word"]}, 41 | Cmd: "!!SDChat", 42 | }, true 43 | } 44 | } 45 | return nil, false 46 | } 47 | -------------------------------------------------------------------------------- /parsers/TpsParser.go: -------------------------------------------------------------------------------- 1 | package parser 2 | 3 | import ( 4 | "MCDaemon-go/command" 5 | "regexp" 6 | ) 7 | 8 | type TpsParser struct{} 9 | 10 | func (p TpsParser) Parsing(word string) (*command.Command, bool) { 11 | re := regexp.MustCompile(`\[\d+:\d+:\d+\]\s+\[Server thread/INFO\]:\s+Stopped debug profiling after (?P.+)`) 12 | match := re.FindStringSubmatch(word) 13 | groupNames := re.SubexpNames() 14 | 15 | result := make(map[string]string) 16 | //匹配tps信息时 17 | if len(match) != 0 { 18 | // 转换为map 19 | for i, name := range groupNames { 20 | result[name] = match[i] 21 | } 22 | _commond := &command.Command{ 23 | Cmd: "!!tps", 24 | Argv: []string{"res", result["TpsInfo"]}, 25 | } 26 | return _commond, true 27 | } 28 | return nil, false 29 | } 30 | -------------------------------------------------------------------------------- /parsers/WarnParser.go: -------------------------------------------------------------------------------- 1 | package parser 2 | 3 | import ( 4 | "MCDaemon-go/command" 5 | "regexp" 6 | ) 7 | 8 | type WarnParser struct{} 9 | 10 | func (p *WarnParser) Parsing(word string) (*command.Command, bool) { 11 | re := regexp.MustCompile(`\[\d+:\d+:\d+\]\s+\[Server thread/WARN\]: Can't keep up! ` + 12 | `Is the server overloaded\? Running (\d*)+ms or (\d*)+ ticks behind`) 13 | match := re.FindStringSubmatch(word) 14 | if len(match) == 3 { 15 | _commond := &command.Command{ 16 | Cmd: "!!Warn", 17 | Argv: []string{"warn", match[1], match[2]}, 18 | } 19 | return _commond, true 20 | } 21 | 22 | return nil, false 23 | } 24 | -------------------------------------------------------------------------------- /parsers/defaultParser.go: -------------------------------------------------------------------------------- 1 | package parser 2 | 3 | import ( 4 | "MCDaemon-go/command" 5 | "MCDaemon-go/config" 6 | "regexp" 7 | "strings" 8 | ) 9 | 10 | //解析玩家输入文字,判断是否是命令 , 实现了Parser接口 11 | type defaultParser struct{} 12 | 13 | //默认语法解析器 14 | func (c *defaultParser) Parsing(word string) (*command.Command, bool) { 15 | re := regexp.MustCompile(`\[\d+:\d+:\d+\]\s+\[Server thread/INFO\]:\s+<(?P.+)>\s+(?P((!|!!)+.+))\s*`) 16 | match := re.FindStringSubmatch(word) 17 | groupNames := re.SubexpNames() 18 | 19 | result := make(map[string]string) 20 | 21 | //匹配到命令时 22 | if len(match) != 0 { 23 | // 转换为map 24 | for i, name := range groupNames { 25 | result[name] = match[i] 26 | } 27 | 28 | // 解析命令以及参数 29 | cmdArgv := strings.Fields(result["commond"]) 30 | _commond := &command.Command{ 31 | Player: result["player"], 32 | Cmd: cmdArgv[0], 33 | Argv: cmdArgv[1:], 34 | } 35 | //获取插件名称 36 | _commond.PluginName = config.GetPluginName(cmdArgv[0]) 37 | return _commond, true 38 | } 39 | return nil, false 40 | } 41 | -------------------------------------------------------------------------------- /parsers/parserList.go: -------------------------------------------------------------------------------- 1 | package parser 2 | 3 | import "MCDaemon-go/lib" 4 | 5 | //语法解析器列表 6 | func CreateParserList() []lib.Parser { 7 | return []lib.Parser{ 8 | &SDChatParser{}, 9 | &ChatParser{}, 10 | &defaultParser{}, 11 | &BackupParser{}, 12 | &TpsParser{}, 13 | &LoginoutParser{}, 14 | &WarnParser{}, 15 | &HereParser{}, 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /plugins/AutoBackupPlugin.go: -------------------------------------------------------------------------------- 1 | // +build darwin linux !windows 2 | 3 | /* 4 | * 自动增量备份插件(仅限Linux,在Ubuntu18.04通过测试) 5 | * 定时运行:rsync -a --delete minecraft/ back-up/auto 6 | * author: Sciroccogti 7 | */ 8 | 9 | package plugin 10 | 11 | import ( 12 | "MCDaemon-go/command" 13 | "MCDaemon-go/config" 14 | "MCDaemon-go/lib" 15 | "fmt" 16 | "os" 17 | "os/exec" 18 | "runtime" 19 | "strconv" 20 | "syscall" 21 | "time" 22 | ) 23 | 24 | type AutoBackup struct { 25 | interval int 26 | workdir string 27 | } 28 | 29 | func (ab *AutoBackup) Handle(c *command.Command, s lib.Server) { 30 | if len(c.Argv) == 0 { 31 | c.Argv = append(c.Argv, "help") 32 | } 33 | 34 | if runtime.GOOS == "windows" { 35 | s.Tell(c.Player, "windows服务器不支持增量备份功能") 36 | } else { 37 | switch c.Argv[0] { 38 | case "set": 39 | if len(c.Argv) < 2 { 40 | s.Tell(c.Player, command.Text{"请输入要设定的小时数!", "red"}) 41 | } else { 42 | interval_new, err := strconv.Atoi(c.Argv[1]) 43 | if err != nil || interval_new < 0 { 44 | s.Tell(c.Player, command.Text{"请输入自然数!", "red"}) 45 | } else if interval_new == 0 { 46 | s.Say(command.Text{"自动存档关闭!", "yellow"}) 47 | ab.interval = interval_new 48 | } else { 49 | s.Say(command.Text{fmt.Sprintf("自动存档间隔设为%d小时", interval_new), "yellow"}) 50 | ab.interval = interval_new 51 | } 52 | } 53 | 54 | case "query": 55 | lastTime, strerr := GetFileChangeTime("back-up/auto") 56 | currentTime := time.Now() 57 | printTime := "上次存档:" 58 | if len(strerr) > 0 { 59 | s.Tell(c.Player, command.Text{strerr, "red"}) 60 | } else { 61 | if currentTime.Year() != lastTime.Year() { 62 | printTime += fmt.Sprintf("%d年", lastTime.Year()) 63 | } 64 | 65 | printTime += fmt.Sprintf("%d月%d日%d时%d分", lastTime.Month(), lastTime.Day(), lastTime.Hour(), lastTime.Minute()) 66 | s.Tell(c.Player, command.Text{printTime, "yellow"}) 67 | } 68 | if ab.interval > 0 && len(ab.workdir) > 0 { 69 | s.Tell(c.Player, command.Text{fmt.Sprintf("间隔为%d小时", ab.interval), "yellow"}) 70 | } else { 71 | s.Tell(c.Player, command.Text{"自动备份已关闭", "yellow"}) 72 | } 73 | 74 | case "save": 75 | if len(c.Argv) > 1 { 76 | s.WriteLog("info", fmt.Sprintf("玩家 %s 下线了", c.Argv[1])) 77 | } 78 | 79 | if ab.interval > 0 && len(ab.workdir) > 0 { 80 | lastTime, strerr := GetFileChangeTime("back-up/auto") 81 | currentTime := time.Now() 82 | if len(strerr) > 0 { 83 | s.Tell(c.Player, command.Text{strerr, "red"}) 84 | } else if currentTime.Unix()-lastTime.Unix() > int64(ab.interval*3600) { 85 | s.Say(command.Text{"开始自动备份...", "yellow"}) 86 | // 冗余备份 87 | cmdlast := exec.Command("rsync", "-a", "--delete", "back-up/auto/", "back-up/auto-last") 88 | errlast := cmdlast.Run() 89 | if errlast != nil { 90 | s.Say(command.Text{fmt.Sprintf("备份错误:%s", errlast), "red"}) 91 | fmt.Println(fmt.Sprintf("备份错误:%s", errlast)) 92 | } else { 93 | // 备份当前存档 94 | cmd := exec.Command("rsync", "-a", "--delete", ab.workdir+"/", "back-up/auto") 95 | err := cmd.Run() 96 | if err != nil { 97 | s.Say(command.Text{fmt.Sprintf("备份错误:%s", err), "red"}) 98 | fmt.Println(fmt.Sprintf("备份错误:%s", err)) 99 | } else { 100 | s.Say(command.Text{"备份完毕", "green"}) 101 | fmt.Println("备份完毕") 102 | s.WriteLog("info", "增量备份完毕") 103 | } 104 | } 105 | 106 | } 107 | } 108 | 109 | default: 110 | set1 := command.Text{"!!autobk set [小时数]", "white"} 111 | set2 := command.Text{"设定存档间隔,0为关闭\n", "green"} 112 | query1 := command.Text{"!!autobk query", "white"} 113 | query2 := command.Text{"查询上次存档时间和间隔时间\n", "green"} 114 | s.Tell(c.Player, set1, set2, query1, query2) 115 | } 116 | } 117 | } 118 | 119 | // GetFileChangeTime :获取文件修改时间 返回时间 120 | func GetFileChangeTime(path string) (t time.Time, strerr string) { 121 | f, err := os.Open(path) 122 | if err != nil { 123 | return time.Now(), "open file error" 124 | } 125 | defer f.Close() 126 | 127 | fi, err := f.Stat() 128 | if err != nil { 129 | return time.Now(), "stat fileinfo error" 130 | } 131 | 132 | filestat := fi.Sys().(*syscall.Stat_t) 133 | return timespecToTime(filestat.Atim), "" 134 | } 135 | 136 | // timespecToTime :将获取到的元信息时间转为Time 137 | func timespecToTime(ts syscall.Timespec) time.Time { 138 | return time.Unix(int64(ts.Sec), int64(ts.Nsec)) 139 | } 140 | 141 | func (ab *AutoBackup) Init(s lib.Server) { 142 | ab.interval = 1 143 | ab.workdir = config.GetPluginCfg(false).Section("AutoBackup").Key("workdir").String() 144 | } 145 | 146 | func (ab *AutoBackup) Close() { 147 | } 148 | -------------------------------------------------------------------------------- /plugins/BackupPlugin/BackupPlugin.go: -------------------------------------------------------------------------------- 1 | package BackupPlugin 2 | 3 | import ( 4 | "MCDaemon-go/command" 5 | "MCDaemon-go/config" 6 | "MCDaemon-go/lib" 7 | "fmt" 8 | "os/exec" 9 | "path/filepath" 10 | "runtime" 11 | "strings" 12 | ) 13 | 14 | type BackupPlugin struct { 15 | backupName string 16 | } 17 | 18 | func (bp *BackupPlugin) Handle(c *command.Command, s lib.Server) { 19 | if len(c.Argv) == 0 { 20 | c.Argv = append(c.Argv, "help") 21 | } 22 | switch c.Argv[0] { 23 | case "save": 24 | if len(c.Argv) < 2 { 25 | s.Tell(c.Player, "请输入备份存档名称!") 26 | } else { 27 | bp.backupName = c.Argv[1] 28 | s.Execute("/save-all flush") 29 | } 30 | case "saved": 31 | server_path := config.Cfg.Section("MCDeamon").Key("server_path").String() 32 | if err := Copy(server_path, "back-up/"+bp.backupName); err != nil { 33 | lib.WriteDevelopLog("error", err.Error()) 34 | } 35 | s.Say("备份完成") 36 | case "compress": 37 | if runtime.GOOS == "windows" { 38 | s.Tell(c.Player, "windows服务器不支持压缩功能") 39 | } else { 40 | cmd := exec.Command("tar", "zcvf", fmt.Sprintf("back-up/%s/%s.tar.gz", bp.backupName, bp.backupName), "back-up/"+bp.backupName) 41 | if err := cmd.Run(); err != nil { 42 | s.Tell(c.Player, fmt.Sprint("压缩姬出问题了,因为", err)) 43 | } else { 44 | s.Tell(c.Player, "压缩备份完成") 45 | } 46 | } 47 | case "show": 48 | backupfiles, _ := filepath.Glob("back-up/*") 49 | //标记是否启动 50 | for k, _ := range backupfiles { 51 | var split string 52 | if runtime.GOOS == "windows" { 53 | split = "\\" 54 | } else { 55 | split = "/" 56 | } 57 | backupfiles[k] = strings.Split(backupfiles[k], split)[1] 58 | } 59 | text := "备份如下:\\n" + strings.Join(backupfiles, "\\n") 60 | s.Tell(c.Player, text) 61 | default: 62 | text := "使用规则:\\n!!backup save [存档名称(最好是日期)]\\nlinux下可使用!!backup compress对最近一次save的存档进行压缩\\n!!backup show查看已备份列表" 63 | s.Tell(c.Player, text) 64 | } 65 | } 66 | 67 | func (bp *BackupPlugin) Init(s lib.Server) { 68 | } 69 | 70 | func (bp *BackupPlugin) Close() { 71 | } 72 | -------------------------------------------------------------------------------- /plugins/BackupPlugin/copy.go: -------------------------------------------------------------------------------- 1 | /** 2 | * 用于备份插件的复制文件夹第三方库 3 | * 来源不明:若源作者看到,并不授权使用,请告知! 4 | */ 5 | 6 | package BackupPlugin 7 | 8 | import ( 9 | "io" 10 | "io/ioutil" 11 | "os" 12 | "path/filepath" 13 | ) 14 | 15 | func Copy(src, dest string) error { 16 | info, err := os.Lstat(src) 17 | if err != nil { 18 | return err 19 | } 20 | return copy(src, dest, info) 21 | } 22 | 23 | func copy(src, dest string, info os.FileInfo) error { 24 | if info.Mode()&os.ModeSymlink != 0 { 25 | return lcopy(src, dest, info) 26 | } 27 | if info.IsDir() { 28 | return dcopy(src, dest, info) 29 | } 30 | return fcopy(src, dest, info) 31 | } 32 | 33 | func fcopy(src, dest string, info os.FileInfo) error { 34 | 35 | if err := os.MkdirAll(filepath.Dir(dest), os.ModePerm); err != nil { 36 | return err 37 | } 38 | 39 | f, err := os.Create(dest) 40 | if err != nil { 41 | return err 42 | } 43 | defer f.Close() 44 | 45 | if err = os.Chmod(f.Name(), info.Mode()); err != nil { 46 | return err 47 | } 48 | 49 | s, err := os.Open(src) 50 | if err != nil { 51 | return err 52 | } 53 | defer s.Close() 54 | 55 | _, err = io.Copy(f, s) 56 | return err 57 | } 58 | 59 | func dcopy(srcdir, destdir string, info os.FileInfo) error { 60 | 61 | if err := os.MkdirAll(destdir, info.Mode()); err != nil { 62 | return err 63 | } 64 | 65 | contents, err := ioutil.ReadDir(srcdir) 66 | if err != nil { 67 | return err 68 | } 69 | 70 | for _, content := range contents { 71 | cs, cd := filepath.Join(srcdir, content.Name()), filepath.Join(destdir, content.Name()) 72 | if err := copy(cs, cd, content); err != nil { 73 | return err 74 | } 75 | } 76 | return nil 77 | } 78 | 79 | func lcopy(src, dest string, info os.FileInfo) error { 80 | src, err := os.Readlink(src) 81 | if err != nil { 82 | return err 83 | } 84 | return os.Symlink(src, dest) 85 | } 86 | -------------------------------------------------------------------------------- /plugins/BasePlugin.go: -------------------------------------------------------------------------------- 1 | /** 2 | *基础插件 3 | *提供服务器停止,启动和重启功能 4 | */ 5 | 6 | package plugin 7 | 8 | import ( 9 | "MCDaemon-go/command" 10 | "MCDaemon-go/lib" 11 | ) 12 | 13 | type BasePlugin struct { 14 | } 15 | 16 | func (hp *BasePlugin) Handle(c *command.Command, s lib.Server) { 17 | if len(c.Argv) == 0 { 18 | c.Argv = append(c.Argv, "help") 19 | } 20 | switch c.Argv[0] { 21 | case "restart": 22 | s.Restart() 23 | case "stop": 24 | lib.WriteDevelopLog("info", "关闭服务器") 25 | s.CloseInContainer() 26 | case "reload": 27 | s.RunUniquePlugin(s.ReloadConf) 28 | case "ban": 29 | if len(c.Argv) > 1 { 30 | if plugin, ok := PluginMap(s.GetPluginList()).DelPlugin(c.Argv[1]); ok { 31 | PluginMap(s.GetDisablePluginList()).RegisterPlugin(c.Argv[1], plugin) 32 | } else { 33 | s.Tell(c.Player, "不存在该插件,建议!!server show list查看可使用的插件") 34 | } 35 | } else { 36 | s.Tell(c.Player, "请输入插件名称") 37 | } 38 | case "pardon": 39 | if len(c.Argv) > 1 { 40 | if plugin, ok := PluginMap(s.GetDisablePluginList()).DelPlugin(c.Argv[1]); ok { 41 | PluginMap(s.GetPluginList()).RegisterPlugin(c.Argv[1], plugin) 42 | } else { 43 | s.Tell(c.Player, "不存在该插件,建议!!server show banlist查看已被禁用的插件") 44 | } 45 | } else { 46 | s.Tell(c.Player, "请输入插件名称") 47 | } 48 | case "show": 49 | if len(c.Argv) > 1 { 50 | if c.Argv[1] == "list" { 51 | var text string 52 | for k, _ := range PluginMap(s.GetPluginList()) { 53 | text += k + "\\n" 54 | } 55 | s.Tell(c.Player, "插件列表:\\n"+text) 56 | } else if c.Argv[1] == "banlist" { 57 | var text string 58 | for k, _ := range PluginMap(s.GetDisablePluginList()) { 59 | text += k + "\\n" 60 | } 61 | s.Tell(c.Player, "已禁用插件列表:\\n"+text) 62 | } 63 | } else { 64 | s.Tell(c.Player, "请输入查看插件类型") 65 | } 66 | default: 67 | text := "!!server restart 重启服务器\\n!!server stop 关闭服务器\\n!!server reload 重新加载热插件\\n!!server ban [插件名] 禁用插件\\n!!server pardon [插件名] 恢复禁用的插件\\n!!server show list 查看插件列表\\n!!server show banlist 查看禁用插件列表" 68 | s.Tell(c.Player, text) 69 | } 70 | } 71 | 72 | func (hp *BasePlugin) Init(s lib.Server) { 73 | } 74 | 75 | func (hp *BasePlugin) Close() { 76 | } 77 | -------------------------------------------------------------------------------- /plugins/ChatPlugin/ChatPlugin.go: -------------------------------------------------------------------------------- 1 | package ChatPlugin 2 | 3 | import ( 4 | "MCDaemon-go/command" 5 | "MCDaemon-go/config" 6 | "MCDaemon-go/container" 7 | "MCDaemon-go/lib" 8 | "context" 9 | "fmt" 10 | "strings" 11 | "sync" 12 | ) 13 | 14 | var ( 15 | WSsvr *WSServer 16 | WSrsPool []WebSocketRS 17 | once *sync.Once 18 | packageChan chan *msgPackage 19 | pluginCtx context.Context 20 | pluginConcel context.CancelFunc 21 | LocalServerName string 22 | IsStart bool 23 | FirstTouch int64 = 1024 24 | NotInWhitelist int64 = 1 25 | ContainerName string 26 | ) 27 | 28 | const ChanMaxSize int = 20 29 | const LocalServerId int = -1 30 | const MinecraftServerId int = -2 31 | 32 | func init() { 33 | once = &sync.Once{} 34 | pluginCtx, pluginConcel = context.WithCancel(context.Background()) 35 | packageChan = make(chan *msgPackage, ChanMaxSize) 36 | } 37 | 38 | //每个服务器的消息包 39 | type msgPackage struct { 40 | From int //服务器id 41 | Msg *Message 42 | } 43 | 44 | type ChatPlugin struct{} 45 | 46 | func (this *ChatPlugin) Handle(c *command.Command, s lib.Server) { 47 | if len(c.Argv) < 1 { 48 | c.Argv = append(c.Argv, "help") 49 | } 50 | switch c.Argv[0] { 51 | case "start": 52 | command.Group.AddPlayer("ServersChat", c.Player) 53 | case "stop": 54 | command.Group.DelPlayer("ServersChat", c.Player) 55 | case "chat_xxx_say": //发送消息 56 | plugincfg := config.GetPluginCfg(false) 57 | contentColor := plugincfg.Section("LinkChat").Key("server_content_color").String() 58 | playerColor := plugincfg.Section("LinkChat").Key("server_player_color").String() 59 | serverColor := plugincfg.Section("LinkChat").Key("server_name_color").String() 60 | msg := &Message{ 61 | ServerName: &LocalServerName, 62 | Player: &(c.Player), 63 | Message: &(c.Argv[1]), 64 | ServerNameColor: &serverColor, 65 | PlayerColor: &playerColor, 66 | MessageColor: &contentColor, 67 | } 68 | packageChan <- &msgPackage{ 69 | From: MinecraftServerId, 70 | Msg: msg, 71 | } 72 | default: 73 | text := "!!Chat start 开启跨服聊天模式\\n!!Chat stop 关闭跨服聊天模式" 74 | s.Tell(c.Player, text) 75 | } 76 | } 77 | 78 | func (this *ChatPlugin) Init(s lib.Server) { 79 | once.Do(func() { 80 | ContainerName = s.GetName() 81 | //开启服务器和连接服务器 82 | start() 83 | if IsStart { 84 | WSsvr.minecraftServer = s 85 | } 86 | //读取消息并发送到本地服务器和镜像内 87 | read() 88 | }) 89 | } 90 | 91 | func (this *ChatPlugin) Close() { 92 | } 93 | 94 | /** 95 | 开启和连接websocket服务器 96 | */ 97 | func start() { 98 | plugincfg := config.GetPluginCfg(true) 99 | IsStart, _ = plugincfg.Section("LinkChat").Key("is_start").Bool() 100 | whitelistArr := plugincfg.Section("LinkChat.whitelist").Key("whitelist").ValueWithShadows() 101 | whitelist := make(map[string]interface{}) 102 | LocalServerName = plugincfg.Section("LinkChat").Key("server_name").String() 103 | 104 | for _, v := range whitelistArr { 105 | whitelist[v] = 1 106 | } 107 | //开启websocket服务器 108 | if IsStart { 109 | //创建服务器实例 110 | port, _ := plugincfg.Section("LinkChat").Key("server_port").Int() 111 | WSSvrCtx, cancel := context.WithCancel(pluginCtx) 112 | WSsvr = &WSServer{ 113 | ServerId: LocalServerId, 114 | Port: port, 115 | Suburl: plugincfg.Section("LinkChat").Key("server_sub_url").String(), 116 | ReceiveMessage: make(chan *msgPackage, ChanMaxSize), 117 | SendMessage: make(chan *Message, ChanMaxSize), 118 | RWPool: &sync.RWMutex{}, 119 | WhiteList: whitelist, 120 | ServerName: LocalServerName, 121 | Ctx: WSSvrCtx, 122 | Cancel: cancel, 123 | Alive: true, 124 | } 125 | go WSsvr.Start() //开启服务器 126 | //加入websocket读写池 127 | WSrsPool = append(WSrsPool, WSsvr) 128 | } 129 | 130 | //连接websocket服务器 131 | Servername := plugincfg.Section("LinkChat.server").KeyStrings() 132 | clientID := 0 133 | for _, v := range Servername { 134 | clientID++ 135 | WSCliCtx, cancel := context.WithCancel(pluginCtx) 136 | WSCli := &WSClient{ 137 | ServerId: clientID, 138 | ServerName: LocalServerName, 139 | addr: plugincfg.Section("LinkChat.server").Key(v).String(), 140 | origin: "TISMCDGO://" + plugincfg.Section("LinkChat").Key("server_name").String(), 141 | ReceiveMessage: make(chan *Message, ChanMaxSize), 142 | Ctx: WSCliCtx, 143 | Cancel: cancel, 144 | } 145 | //发送初探消息 146 | 147 | go WSCli.Start() //连接服务器 148 | WSrsPool = append(WSrsPool, WSCli) 149 | } 150 | 151 | //开启消息发送 152 | go SendMessage() 153 | } 154 | 155 | /** 156 | 多协程读取websocket读发池中收到的消息 157 | */ 158 | func read() { 159 | if WSrsPool != nil { 160 | for _, rs := range WSrsPool { 161 | go rs.Read() 162 | } 163 | } 164 | } 165 | 166 | /** 167 | 向网络服务器发送消息 168 | */ 169 | func sendNetServer(pkg *msgPackage) { 170 | var releaseIndexs []int 171 | if WSrsPool != nil { 172 | for k, rs := range WSrsPool { 173 | //死链接,加入待释放池 174 | if !rs.IsAlive() { 175 | releaseIndexs = append(releaseIndexs, k) 176 | continue 177 | } 178 | if rs.GetId() != pkg.From { 179 | // serverName := rs.GetName() 180 | // pkg.Msg.ServerName = &serverName 181 | go rs.Send(pkg.Msg) 182 | } 183 | } 184 | } 185 | //删除死链接 186 | if len(releaseIndexs) != 0 { 187 | for _, index := range releaseIndexs { 188 | lib.WriteDevelopLog("info", fmt.Sprint(WSrsPool[index].GetName(), "已死亡")) 189 | WSrsPool = append(WSrsPool[:index], WSrsPool[index+1:]...) 190 | } 191 | } 192 | } 193 | 194 | /** 195 | 向本机游戏服务器发送消息 196 | */ 197 | func sendLocalServer(pkg *msgPackage) { 198 | //是本机发送就不发 199 | if WSrsPool != nil { 200 | serverPool := container.GetInstance().Servers 201 | for serverName, server := range serverPool { 202 | if pkg.From == MinecraftServerId && ContainerName == serverName { 203 | continue 204 | } 205 | // 发送给用户组消息 206 | players := command.Group.GetPlayer()["ServersChat"] 207 | for _, player := range players { 208 | server.Tell(player, 209 | command.Text{"[" + pkg.Msg.GetServerName() + "]", pkg.Msg.GetServerNameColor()}, 210 | command.Text{pkg.Msg.GetPlayer(), pkg.Msg.GetPlayerColor()}, 211 | command.Text{":", "white"}, 212 | command.Text{strings.Replace(pkg.Msg.GetMessage(), "\r", "", -1), pkg.Msg.GetMessageColor()}, 213 | ) 214 | } 215 | } 216 | } 217 | } 218 | 219 | /** 220 | 接受消息并发送 221 | */ 222 | func SendMessage() { 223 | for { 224 | select { 225 | case <-pluginCtx.Done(): 226 | lib.WriteDevelopLog("warn", "消息发送上下文已关闭") 227 | return 228 | case message := <-packageChan: 229 | sendNetServer(message) 230 | sendLocalServer(message) 231 | } 232 | } 233 | } 234 | -------------------------------------------------------------------------------- /plugins/ChatPlugin/chatBuf.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go. DO NOT EDIT. 2 | // source: chatBuf.proto 3 | 4 | package ChatPlugin 5 | 6 | import ( 7 | fmt "fmt" 8 | proto "github.com/golang/protobuf/proto" 9 | math "math" 10 | ) 11 | 12 | // Reference imports to suppress errors if they are not otherwise used. 13 | var _ = proto.Marshal 14 | var _ = fmt.Errorf 15 | var _ = math.Inf 16 | 17 | // This is a compile-time assertion to ensure that this generated file 18 | // is compatible with the proto package it is being compiled against. 19 | // A compilation error at this line likely means your copy of the 20 | // proto package needs to be updated. 21 | const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package 22 | 23 | type Message struct { 24 | ServerName *string `protobuf:"bytes,1,req,name=ServerName" json:"ServerName,omitempty"` 25 | Player *string `protobuf:"bytes,2,opt,name=Player" json:"Player,omitempty"` 26 | Message *string `protobuf:"bytes,3,opt,name=Message" json:"Message,omitempty"` 27 | ServerNameColor *string `protobuf:"bytes,4,opt,name=ServerNameColor,def=white" json:"ServerNameColor,omitempty"` 28 | PlayerColor *string `protobuf:"bytes,5,opt,name=PlayerColor,def=white" json:"PlayerColor,omitempty"` 29 | MessageColor *string `protobuf:"bytes,6,opt,name=MessageColor,def=white" json:"MessageColor,omitempty"` 30 | State *int64 `protobuf:"varint,7,opt,name=state" json:"state,omitempty"` 31 | XXX_NoUnkeyedLiteral struct{} `json:"-"` 32 | XXX_unrecognized []byte `json:"-"` 33 | XXX_sizecache int32 `json:"-"` 34 | } 35 | 36 | func (m *Message) Reset() { *m = Message{} } 37 | func (m *Message) String() string { return proto.CompactTextString(m) } 38 | func (*Message) ProtoMessage() {} 39 | func (*Message) Descriptor() ([]byte, []int) { 40 | return fileDescriptor_c340e74c0f2a7ab8, []int{0} 41 | } 42 | 43 | func (m *Message) XXX_Unmarshal(b []byte) error { 44 | return xxx_messageInfo_Message.Unmarshal(m, b) 45 | } 46 | func (m *Message) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { 47 | return xxx_messageInfo_Message.Marshal(b, m, deterministic) 48 | } 49 | func (m *Message) XXX_Merge(src proto.Message) { 50 | xxx_messageInfo_Message.Merge(m, src) 51 | } 52 | func (m *Message) XXX_Size() int { 53 | return xxx_messageInfo_Message.Size(m) 54 | } 55 | func (m *Message) XXX_DiscardUnknown() { 56 | xxx_messageInfo_Message.DiscardUnknown(m) 57 | } 58 | 59 | var xxx_messageInfo_Message proto.InternalMessageInfo 60 | 61 | const Default_Message_ServerNameColor string = "white" 62 | const Default_Message_PlayerColor string = "white" 63 | const Default_Message_MessageColor string = "white" 64 | 65 | func (m *Message) GetServerName() string { 66 | if m != nil && m.ServerName != nil { 67 | return *m.ServerName 68 | } 69 | return "" 70 | } 71 | 72 | func (m *Message) GetPlayer() string { 73 | if m != nil && m.Player != nil { 74 | return *m.Player 75 | } 76 | return "" 77 | } 78 | 79 | func (m *Message) GetMessage() string { 80 | if m != nil && m.Message != nil { 81 | return *m.Message 82 | } 83 | return "" 84 | } 85 | 86 | func (m *Message) GetServerNameColor() string { 87 | if m != nil && m.ServerNameColor != nil { 88 | return *m.ServerNameColor 89 | } 90 | return Default_Message_ServerNameColor 91 | } 92 | 93 | func (m *Message) GetPlayerColor() string { 94 | if m != nil && m.PlayerColor != nil { 95 | return *m.PlayerColor 96 | } 97 | return Default_Message_PlayerColor 98 | } 99 | 100 | func (m *Message) GetMessageColor() string { 101 | if m != nil && m.MessageColor != nil { 102 | return *m.MessageColor 103 | } 104 | return Default_Message_MessageColor 105 | } 106 | 107 | func (m *Message) GetState() int64 { 108 | if m != nil && m.State != nil { 109 | return *m.State 110 | } 111 | return 0 112 | } 113 | 114 | func init() { 115 | proto.RegisterType((*Message)(nil), "ChatPlugin.Message") 116 | } 117 | 118 | func init() { proto.RegisterFile("chatBuf.proto", fileDescriptor_c340e74c0f2a7ab8) } 119 | 120 | var fileDescriptor_c340e74c0f2a7ab8 = []byte{ 121 | // 183 bytes of a gzipped FileDescriptorProto 122 | 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xe2, 0x4d, 0xce, 0x48, 0x2c, 123 | 0x71, 0x2a, 0x4d, 0xd3, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0xe2, 0x72, 0xce, 0x48, 0x2c, 0x09, 124 | 0xc8, 0x29, 0x4d, 0xcf, 0xcc, 0x53, 0xfa, 0xc6, 0xc8, 0xc5, 0xee, 0x9b, 0x5a, 0x5c, 0x9c, 0x98, 125 | 0x9e, 0x2a, 0x24, 0xc7, 0xc5, 0x15, 0x9c, 0x5a, 0x54, 0x96, 0x5a, 0xe4, 0x97, 0x98, 0x9b, 0x2a, 126 | 0xc1, 0xa8, 0xc0, 0xa4, 0xc1, 0x19, 0x84, 0x24, 0x22, 0x24, 0xc6, 0xc5, 0x16, 0x90, 0x93, 0x58, 127 | 0x99, 0x5a, 0x24, 0xc1, 0xa4, 0xc0, 0xa8, 0xc1, 0x19, 0x04, 0xe5, 0x09, 0x49, 0xc0, 0x8d, 0x90, 128 | 0x60, 0x06, 0x4b, 0xc0, 0x4d, 0xd4, 0xe7, 0xe2, 0x47, 0xe8, 0x77, 0xce, 0xcf, 0xc9, 0x2f, 0x92, 129 | 0x60, 0x01, 0xa9, 0xb0, 0x62, 0x2d, 0xcf, 0xc8, 0x2c, 0x49, 0x0d, 0x42, 0x97, 0x15, 0x52, 0xe7, 130 | 0xe2, 0x86, 0x18, 0x0a, 0x51, 0xcc, 0x8a, 0xac, 0x18, 0x59, 0x46, 0x48, 0x93, 0x8b, 0x07, 0x6a, 131 | 0x09, 0x44, 0x25, 0x1b, 0xb2, 0x4a, 0x14, 0x29, 0x21, 0x11, 0x2e, 0xd6, 0xe2, 0x92, 0xc4, 0x92, 132 | 0x54, 0x09, 0x76, 0x05, 0x46, 0x0d, 0xe6, 0x20, 0x08, 0x07, 0x10, 0x00, 0x00, 0xff, 0xff, 0xb3, 133 | 0x76, 0xbe, 0x5e, 0x14, 0x01, 0x00, 0x00, 134 | } 135 | -------------------------------------------------------------------------------- /plugins/ChatPlugin/chatBuf.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto2"; 2 | 3 | package ChatPlugin; 4 | 5 | message Message { 6 | required string ServerName = 1; 7 | optional string Player = 2; 8 | optional string Message = 3; 9 | optional string ServerNameColor = 4 [default="white"]; 10 | optional string PlayerColor = 5 [default="white"]; 11 | optional string MessageColor = 6 [default="white"]; 12 | optional int64 state = 7; 13 | } -------------------------------------------------------------------------------- /plugins/ChatPlugin/webSocketClient.go: -------------------------------------------------------------------------------- 1 | package ChatPlugin 2 | 3 | import ( 4 | "MCDaemon-go/lib" 5 | "context" 6 | "fmt" 7 | 8 | "github.com/golang/protobuf/proto" 9 | "golang.org/x/net/websocket" 10 | ) 11 | 12 | type WSClient struct { 13 | ServerId int //服务器id 14 | ServerName string //服务器名称 15 | addr string 16 | origin string 17 | ws *websocket.Conn //websocket连接 18 | ReceiveMessage chan *Message //接受到的消息 19 | Ctx context.Context //上下文 20 | Cancel context.CancelFunc 21 | } 22 | 23 | func (this *WSClient) Start() error { 24 | var err error 25 | 26 | this.ws, err = websocket.Dial("ws://"+this.addr, "", this.origin) 27 | if err != nil { 28 | lib.WriteDevelopLog("error", err.Error()) 29 | return err 30 | } 31 | defer this.ws.Close() 32 | defer lib.WriteDevelopLog("error", "连接") 33 | this.Send(&Message{ 34 | ServerName: &LocalServerName, 35 | State: &FirstTouch, 36 | }) 37 | for { 38 | msg := make([]byte, 5096) 39 | slen, err := this.ws.Read(msg) //此处阻塞,等待有数据可读 40 | msg = msg[:slen] 41 | if err != nil { 42 | lib.WriteDevelopLog("error", fmt.Sprint("读取错误:", err)) 43 | //如果连接出错,则释放连接 44 | break 45 | } 46 | newMessage := &Message{} 47 | err = proto.Unmarshal(msg, newMessage) 48 | if err != nil { 49 | lib.WriteDevelopLog("error", fmt.Sprint("解码错误:", err, "内容:", msg)) 50 | break 51 | } 52 | if newMessage.GetState() != 0 { 53 | lib.WriteDevelopLog("error", "聊天服务器连接失败:不再白名单内!") 54 | break 55 | } 56 | this.ReceiveMessage <- newMessage 57 | } 58 | this.Cancel() 59 | return err 60 | } 61 | 62 | func (this *WSClient) Send(msg *Message) { 63 | data, err := proto.Marshal(msg) 64 | if err != nil { 65 | lib.WriteDevelopLog("error", fmt.Sprint("加密错误:", err)) 66 | return 67 | } 68 | err = websocket.Message.Send(this.ws, data) 69 | if err != nil { 70 | lib.WriteDevelopLog("error", fmt.Sprint(this.GetName, "发送信息错误:", err)) 71 | } 72 | } 73 | 74 | func (this *WSClient) Read() { 75 | for { 76 | select { 77 | case <-this.Ctx.Done(): 78 | return 79 | case msg := <-this.ReceiveMessage: 80 | packageChan <- &msgPackage{ 81 | From: this.ServerId, 82 | Msg: msg, 83 | } 84 | } 85 | } 86 | } 87 | 88 | func (this *WSClient) GetId() int { 89 | return this.ServerId 90 | } 91 | 92 | func (this *WSClient) GetName() string { 93 | return this.ServerName 94 | } 95 | 96 | func (this *WSClient) IsAlive() bool { 97 | if this.ws != nil { 98 | return true 99 | } 100 | this.Cancel() 101 | return false 102 | } 103 | -------------------------------------------------------------------------------- /plugins/ChatPlugin/webSocketInterface.go: -------------------------------------------------------------------------------- 1 | package ChatPlugin 2 | 3 | type WebSocketRS interface { 4 | Start() error 5 | Send(*Message) 6 | Read() 7 | GetId() int 8 | GetName() string 9 | IsAlive() bool 10 | } 11 | -------------------------------------------------------------------------------- /plugins/ChatPlugin/webSocketServer.go: -------------------------------------------------------------------------------- 1 | /** 2 | websocket客户端 3 | */ 4 | package ChatPlugin 5 | 6 | import ( 7 | "MCDaemon-go/lib" 8 | "context" 9 | "fmt" 10 | "net/http" 11 | "strconv" 12 | "sync" 13 | 14 | "github.com/golang/protobuf/proto" 15 | "golang.org/x/net/websocket" 16 | ) 17 | 18 | var clientID = 10000 19 | 20 | type WSServerClient struct { 21 | ServerId int //服务器id 22 | Conn *websocket.Conn 23 | } 24 | 25 | type WSServer struct { 26 | ServerId int //服务器id 27 | ServerName string //服务器名称 28 | Port int 29 | Suburl string //子路由 30 | ReceiveMessage chan *msgPackage //接受到的消息 31 | SendMessage chan *Message //要发送的消息 32 | origin string //源地址 33 | minecraftServer lib.Server //服务器实例接口 34 | ConnPool map[string]*WSServerClient //连接池,键为服务器名称,值为封装的websocket连接对象 35 | RWPool *sync.RWMutex //连接池读写锁 36 | WhiteList map[string]interface{} //白名单 37 | Ctx context.Context //上下文 38 | Cancel context.CancelFunc 39 | Alive bool //是否是活跃连接 40 | } 41 | 42 | func (this *WSServer) handler(conn *websocket.Conn) { 43 | fmt.Println("聊天服务器连接成功") 44 | defer conn.Close() 45 | var err error 46 | clientID = clientID + 1 47 | Cilent := &WSServerClient{clientID, conn} 48 | for { 49 | var reply []byte 50 | err = websocket.Message.Receive(conn, &reply) 51 | if err != nil { 52 | lib.WriteDevelopLog("error", fmt.Sprint("聊天服务器出错:", err)) 53 | break 54 | } 55 | //将proto消息解码 56 | newMessage := &Message{} 57 | err = proto.Unmarshal(reply, newMessage) 58 | if err != nil { 59 | lib.WriteDevelopLog("warn", fmt.Sprint("非法连接:", conn.RemoteAddr().String())) 60 | break 61 | } 62 | serverName := newMessage.GetServerName() 63 | //加入到连接池中,若不在聊天白名单中,则关闭连接 64 | if ok := this.appendToConnPool(serverName, Cilent); !ok { 65 | data, _ := proto.Marshal(&Message{ 66 | ServerName: &LocalServerName, 67 | State: &NotInWhitelist, 68 | }) 69 | websocket.Message.Send(conn, data) 70 | lib.WriteDevelopLog("warn", fmt.Sprint("不在白名单中:", serverName)) 71 | break 72 | } 73 | fmt.Println("1---", newMessage) 74 | if newMessage.GetState() == FirstTouch { 75 | fmt.Println("1---", newMessage) 76 | continue 77 | } 78 | //将消息发送给其他连接 79 | clientMsg := &msgPackage{Cilent.ServerId, newMessage} 80 | this.SendtoClient(clientMsg) 81 | //将消息加入到接收管道中 82 | ServerMsg := &msgPackage{LocalServerId, newMessage} 83 | this.ReceiveMessage <- ServerMsg 84 | } 85 | } 86 | 87 | //向连接池里的所有连接发送消息 88 | func (this *WSServer) SendtoClient(msg *msgPackage) { 89 | //编码 90 | data, _ := proto.Marshal(msg.Msg) 91 | for serverName, client := range this.ConnPool { 92 | //如果serverid相同,则不发送 93 | if client.ServerId == msg.From { 94 | continue 95 | } 96 | //若出现错误,则从连接池中删除并关闭这条连接 97 | fmt.Println("向子连接发送", client.ServerId, "------", msg.From) 98 | if err := websocket.Message.Send(client.Conn, data); err != nil { 99 | this.deletePool(serverName) 100 | client.Conn.Close() 101 | break 102 | } 103 | } 104 | } 105 | 106 | //接收要发送的消息 107 | func (this *WSServer) Send(msg *Message) { 108 | this.SendtoClient(&msgPackage{LocalServerId, msg}) 109 | } 110 | 111 | func (this *WSServer) Read() { 112 | for { 113 | select { 114 | case <-this.Ctx.Done(): 115 | fmt.Println("server context over!") 116 | return 117 | case msg := <-this.ReceiveMessage: 118 | packageChan <- msg 119 | } 120 | } 121 | } 122 | 123 | //将websocket连接加入到连接池中 124 | func (this *WSServer) appendToConnPool(serverName string, Client *WSServerClient) bool { 125 | if _, ok := this.WhiteList[serverName]; ok { 126 | //如果没有进入连接池,则加入到连接池中 127 | if _, ok := this.readPool(serverName); !ok { 128 | this.writePool(serverName, Client) 129 | } 130 | return true 131 | } 132 | return false 133 | } 134 | 135 | func (this *WSServer) readPool(serverName string) (*WSServerClient, bool) { 136 | this.RWPool.RLock() 137 | defer this.RWPool.RUnlock() 138 | if val, ok := this.ConnPool[serverName]; ok { 139 | return val, true 140 | } 141 | return nil, false 142 | } 143 | 144 | func (this *WSServer) writePool(serverName string, Client *WSServerClient) { 145 | this.RWPool.Lock() 146 | defer this.RWPool.Unlock() 147 | this.ConnPool[serverName] = Client 148 | } 149 | 150 | func (this *WSServer) deletePool(serverName string) { 151 | this.RWPool.Lock() 152 | defer this.RWPool.Unlock() 153 | delete(this.ConnPool, serverName) 154 | } 155 | 156 | func (this *WSServer) Start() error { 157 | url := "localhost:" + strconv.Itoa(this.Port) 158 | http.Handle("/"+this.Suburl, websocket.Handler(this.handler)) 159 | this.ConnPool = make(map[string]*WSServerClient) 160 | lib.WriteDevelopLog("info", fmt.Sprint("websocket启动,连接地址:", url, "/", this.Suburl)) 161 | go http.ListenAndServe(url, nil) 162 | this.Alive = true 163 | return nil 164 | } 165 | 166 | func (this *WSServer) GetId() int { 167 | return this.ServerId 168 | } 169 | 170 | func (this *WSServer) GetName() string { 171 | return this.ServerName 172 | } 173 | 174 | func (this *WSServer) IsAlive() bool { 175 | if this.Alive { 176 | return true 177 | } 178 | this.Cancel() 179 | return false 180 | } 181 | -------------------------------------------------------------------------------- /plugins/ExampleYinyinPlugin.go: -------------------------------------------------------------------------------- 1 | /* 2 | * 插件编写例子 3 | * 当玩家输入命令:!!yinyinmaster 4 | * 他会对所有人说:嘤嘤嘤 5 | */ 6 | 7 | package plugin 8 | 9 | import ( 10 | "MCDaemon-go/command" 11 | "MCDaemon-go/lib" 12 | "fmt" 13 | ) 14 | 15 | type Yinyin struct { 16 | } 17 | 18 | func (hp *Yinyin) Handle(c *command.Command, s lib.Server) { 19 | s.Say(fmt.Sprintf("%s对所有人说:嘤嘤嘤!", c.Player)) 20 | } 21 | 22 | func (hp *Yinyin) Init(s lib.Server) { 23 | } 24 | 25 | func (hp *Yinyin) Close() { 26 | } 27 | -------------------------------------------------------------------------------- /plugins/HerePlugin.go: -------------------------------------------------------------------------------- 1 | /* 2 | * 统计信息助手 3 | * author: Sciroccogti 4 | * 参考:https://github.com/TISUnion/Here 5 | */ 6 | 7 | package plugin 8 | 9 | import ( 10 | "MCDaemon-go/command" 11 | "MCDaemon-go/lib" 12 | ) 13 | 14 | type HerePlugin struct { 15 | highlight bool // 是否开启高亮功能 16 | player string // 使用插件的玩家 17 | dim_convert map[string]string // 编号转维度 18 | } 19 | 20 | func (p *HerePlugin) Handle(c *command.Command, s lib.Server) { 21 | if len(c.Argv) == 0 { 22 | if p.highlight { 23 | s.Tell(c.Player, command.Text{"您将会被高亮15秒", "yellow"}) 24 | s.Execute("/effect give " + c.Player + " minecraft:glowing 15 1 true") 25 | } 26 | s.Execute("/data get entity " + c.Player) 27 | p.player = c.Player 28 | 29 | } else { 30 | switch c.Argv[0] { 31 | case "res": 32 | if len(c.Argv) == 5 { // && c.Argv[1] == p.player { 33 | posstr := "[x:" + c.Argv[2] + ", y:" + c.Argv[3] + ", z:" + c.Argv[4] + "]" 34 | s.Say(command.Text{p.player + "在" + p.dim_convert[c.Argv[1]] + posstr + "向你问好", "green"}) 35 | p.player = "" 36 | } 37 | 38 | case "set": 39 | if len(c.Argv) < 2 { 40 | s.Tell(c.Player, command.Text{"请输入 on 或 off!", "red"}) 41 | } else { 42 | if c.Argv[1] == "off" { 43 | p.highlight = false 44 | s.Say(command.Text{"插件 here 高亮功能已关闭", "yellow"}) 45 | } else if c.Argv[1] == "on" { 46 | p.highlight = true 47 | s.Say(command.Text{"插件 here 高亮功能已开启", "yellow"}) 48 | } else { 49 | s.Tell(c.Player, command.Text{"请输入 on 或 off!", "red"}) 50 | } 51 | } 52 | 53 | default: 54 | here1 := command.Text{"!!here", "white"} 55 | here2 := command.Text{"广播自己的位置\n", "green"} 56 | set1 := command.Text{"!!here set [on/off]", "white"} 57 | set2 := command.Text{"开/关高亮功能,默认开\n", "green"} 58 | s.Tell(c.Player, here1, here2, set1, set2) 59 | } 60 | } 61 | 62 | } 63 | 64 | func (p *HerePlugin) Init(s lib.Server) { 65 | p.highlight = true 66 | p.dim_convert = make(map[string]string) 67 | p.dim_convert["0"] = "主世界" 68 | p.dim_convert["1"] = "末地" 69 | p.dim_convert["-1"] = "地狱" 70 | } 71 | 72 | func (p *HerePlugin) Close() { 73 | } 74 | -------------------------------------------------------------------------------- /plugins/ImagePlugin.go: -------------------------------------------------------------------------------- 1 | /** 2 | * 开启、关闭镜像 3 | * 前置插件: 备份插件backup 4 | */ 5 | 6 | package plugin 7 | 8 | import ( 9 | "MCDaemon-go/command" 10 | "MCDaemon-go/container" 11 | "MCDaemon-go/lib" 12 | "os" 13 | "path/filepath" 14 | "runtime" 15 | "strings" 16 | 17 | "github.com/go-ini/ini" 18 | ) 19 | 20 | type ImagePlugin struct{} 21 | 22 | func (lp *ImagePlugin) Handle(c *command.Command, s lib.Server) { 23 | if len(c.Argv) == 0 { 24 | c.Argv = append(c.Argv, "help") 25 | } 26 | cor := container.GetInstance() 27 | switch c.Argv[0] { 28 | case "show": 29 | backupfiles, _ := filepath.Glob("back-up/*") 30 | //标记是否启动 31 | for k, _ := range backupfiles { 32 | var split string 33 | if runtime.GOOS == "windows" { 34 | split = "\\" 35 | } else { 36 | split = "/" 37 | } 38 | backupfiles[k] = strings.Split(backupfiles[k], split)[1] 39 | if cor.IsRuntime(backupfiles[k]) { 40 | backupfiles[k] += " 已启动" + " 端口:" + cor.Servers[backupfiles[k]].GetPort() 41 | } else { 42 | backupfiles[k] += " 未启动" 43 | } 44 | } 45 | text := "备份如下:\\n" + strings.Join(backupfiles, "\\n") 46 | s.Tell(c.Player, text) 47 | case "start": 48 | if len(c.Argv) == 1 { 49 | s.Tell(c.Player, "缺少启动的镜像名称") 50 | } 51 | //镜像已启动则不启动镜像 52 | if cor.IsRuntime(c.Argv[1]) { 53 | s.Tell(c.Player, "该镜像已经启动") 54 | } else { 55 | path := "back-up/" + c.Argv[1] + "/server.properties" 56 | if _, err := os.Stat(path); err != nil { 57 | s.Tell(c.Player, "镜像不存在") 58 | } else { 59 | cor = container.GetInstance() 60 | svr := s.Clone() 61 | //修改端口 62 | sercfg, _ := ini.Load(path) 63 | sercfg.Section("").NewKey("server-port", svr.GetPort()) 64 | sercfg.SaveTo(path) 65 | //启动 66 | cor.Add(c.Argv[1], "back-up/"+c.Argv[1], svr) 67 | } 68 | } 69 | case "stop": 70 | if len(c.Argv) == 1 { 71 | s.Tell(c.Player, "缺少停止的镜像名称") 72 | } 73 | cor = container.GetInstance() 74 | if cor.IsRuntime(c.Argv[1]) { 75 | cor.Del(c.Argv[1]) 76 | } else { 77 | s.Tell(c.Player, "镜像未启动") 78 | } 79 | default: 80 | text := "!!image show 查看镜像\\n!!image start [镜像名称] 开启镜像 \\n!!image stop [镜像名称] 关闭镜像" 81 | s.Tell(c.Player, text) 82 | } 83 | } 84 | 85 | func (lp *ImagePlugin) Init(s lib.Server) { 86 | } 87 | 88 | func (hp *ImagePlugin) Close() { 89 | } 90 | -------------------------------------------------------------------------------- /plugins/PluginMap.go: -------------------------------------------------------------------------------- 1 | package plugin 2 | 3 | import ( 4 | "MCDaemon-go/config" 5 | "MCDaemon-go/lib" 6 | ) 7 | 8 | type PluginMap map[string]lib.Plugin 9 | 10 | //加载热插件 11 | func (pm PluginMap) GetHotPlugins(is_reload bool) { 12 | plugins := config.GetPlugins(is_reload) 13 | for k, v := range plugins { 14 | pm[k] = &HotPlugin{v} 15 | } 16 | } 17 | 18 | //注册冷插件 19 | func (pm PluginMap) RegisterPlugin(name string, lp lib.Plugin) { 20 | pm[name] = lp 21 | } 22 | 23 | //移除插件 24 | func (pm PluginMap) DelPlugin(name string) (lib.Plugin, bool) { 25 | if pm[name] == nil { 26 | return nil, false 27 | } 28 | plugin := pm[name] 29 | delete(pm, name) 30 | return plugin, true 31 | } 32 | -------------------------------------------------------------------------------- /plugins/SDChatPlugin.go: -------------------------------------------------------------------------------- 1 | package plugin 2 | 3 | import ( 4 | "MCDaemon-go/command" 5 | "MCDaemon-go/config" 6 | "MCDaemon-go/lib" 7 | "encoding/json" 8 | "fmt" 9 | "hash/crc32" 10 | "io/ioutil" 11 | "log" 12 | "net/http" 13 | "strings" 14 | 15 | "github.com/tidwall/gjson" 16 | ) 17 | 18 | type SDChatPlugin struct{} 19 | 20 | func (hp *SDChatPlugin) Handle(c *command.Command, s lib.Server) { 21 | if len(c.Argv) < 1 { 22 | c.Argv = append(c.Argv, "help") 23 | } 24 | switch c.Argv[0] { 25 | case "all": 26 | command.Group.AddPlayer("SDChat-all", c.Player) 27 | s.Tell(c.Player, "开启全局聊天模式成功") 28 | case "start": 29 | command.Group.AddPlayer("SDChat", c.Player) 30 | s.Tell(c.Player, "开启聊天模式成功") 31 | case "stop": 32 | command.Group.DelPlayer("SDChat", c.Player) 33 | command.Group.DelPlayer("SDChat-all", c.Player) 34 | s.Tell(c.Player, "退出聊天模式成功") 35 | case "say": 36 | s.Tell(c.Player, "沙雕:"+chat(c.Argv[1], c.Player)) 37 | case "say-all": 38 | s.Say("沙雕对:" + c.Player + "说" + chat(c.Argv[1], c.Player)) 39 | case "reload": 40 | _ = config.GetPluginCfg(true) 41 | s.Tell(c.Player, "已重新读取配置文件") 42 | default: 43 | text := "!!SDChat all start 开启全局聊天模式\\n!!SDChat start 开启私聊模式(别的玩家看不见沙雕机器人给你发的信息)\\n!!SDChat stop 关闭聊天模式\\n!!SDChat reload 重新加载配置文件" 44 | s.Tell(c.Player, text) 45 | } 46 | } 47 | 48 | func (hp *SDChatPlugin) Init(s lib.Server) { 49 | } 50 | 51 | func (hp *SDChatPlugin) Close() { 52 | } 53 | 54 | //封装JSON 55 | func LightEncode(elememt interface{}) string { 56 | //拼接的结果字符串 57 | var s string 58 | //若为对象,则拼接字符串 59 | if LightJson, err := elememt.(map[string]interface{}); !err { 60 | s = string("{") 61 | for key, val := range LightJson { 62 | s += fmt.Sprintf("\"%s\":\"%s\",", key, LightEncode(val)) 63 | } 64 | s += string("}") 65 | } else { 66 | jsonStr, err := json.Marshal(elememt) 67 | if err != nil { 68 | log.Fatal("Can't transform jsonString,Because ", err) 69 | } 70 | s = string(jsonStr) 71 | } 72 | return s 73 | } 74 | 75 | //http POST请求 76 | func httpPost(data string) string { 77 | resp, err := http.Post("http://openapi.tuling123.com/openapi/api/v2", 78 | "application/x-www-form-urlencoded", 79 | strings.NewReader(data), 80 | ) 81 | if err != nil { 82 | lib.WriteDevelopLog("error", err.Error()) 83 | } 84 | 85 | defer resp.Body.Close() 86 | body, err := ioutil.ReadAll(resp.Body) 87 | if err != nil { 88 | lib.WriteDevelopLog("error", err.Error()) 89 | } 90 | 91 | return string(body) 92 | } 93 | 94 | func stringToInt(s string) int { 95 | v := int(crc32.ChecksumIEEE([]byte(s))) 96 | if v >= 0 { 97 | return v 98 | } 99 | if -v >= 0 { 100 | return -v 101 | } 102 | // v == MinInt 103 | return 0 104 | } 105 | 106 | //发出请求获取聊天回复 107 | func chat(data string, player string) string { 108 | _requestMap := map[string]interface{}{ 109 | "perception": map[string]interface{}{ 110 | "inputText": map[string]interface{}{ 111 | "text": data, 112 | }, 113 | }, 114 | "userInfo": map[string]interface{}{ 115 | "apiKey": config.GetPluginCfg(false).Section("SDChat").Key("appid").String(), 116 | "userId": stringToInt(player), 117 | "groupId": 10, 118 | "userIdName": player, 119 | }, 120 | } 121 | return gjson.Get(httpPost(LightEncode(_requestMap)), "results.0.values.text").String() 122 | } 123 | -------------------------------------------------------------------------------- /plugins/StatsHelperPlugin.go: -------------------------------------------------------------------------------- 1 | /* 2 | * 统计信息助手 3 | * author: Sciroccogti 4 | */ 5 | 6 | package plugin 7 | 8 | import ( 9 | "MCDaemon-go/command" 10 | "MCDaemon-go/config" 11 | "MCDaemon-go/lib" 12 | "fmt" 13 | ) 14 | 15 | type StatsHelper struct { 16 | statsmap map[string]string // [代号] 显示名称 17 | } 18 | 19 | func (p *StatsHelper) Handle(c *command.Command, s lib.Server) { 20 | if len(c.Argv) == 0 { 21 | c.Argv = append(c.Argv, "help") 22 | } 23 | 24 | switch c.Argv[0] { 25 | case "list": 26 | s.Tell(c.Player, command.Text{"现有榜单如下([榜单代号] 显示名称):", "yellow"}) 27 | for stats := range p.statsmap { 28 | s.Tell(c.Player, command.Text{fmt.Sprintf("[%s] %s", stats, p.statsmap[stats]), "yellow"}) 29 | } 30 | 31 | case "set": 32 | if len(c.Argv) < 2 { 33 | s.Tell(c.Player, command.Text{"请输入榜单代号!", "red"}) 34 | } else { 35 | if c.Argv[1] == "off" { 36 | s.Execute(fmt.Sprintf("/scoreboard objectives setdisplay sidebar")) 37 | s.Say(command.Text{"榜单已关闭显示", "yellow"}) 38 | } else { 39 | board, ok := p.statsmap[c.Argv[1]] 40 | if ok { 41 | s.Execute(fmt.Sprintf("/scoreboard objectives setdisplay sidebar %s", c.Argv[1])) 42 | s.Say(command.Text{"榜单已显示为 " + board, "yellow"}) 43 | } else { 44 | s.Tell(c.Player, command.Text{"请检查输入的榜单代号!", "red"}) 45 | } 46 | } 47 | } 48 | 49 | default: 50 | list1 := command.Text{"!!stats list", "white"} 51 | list2 := command.Text{"列出所有榜单\n", "green"} 52 | set1 := command.Text{"!!stats set [榜单代号(英文)]", "white"} 53 | set2 := command.Text{"设置要显示的榜单,off为关闭\n", "green"} 54 | s.Tell(c.Player, list1, list2, set1, set2) 55 | } 56 | 57 | } 58 | 59 | func (p *StatsHelper) Init(s lib.Server) { 60 | p.statsmap = make(map[string]string) 61 | p.statsmap = config.GetPluginCfg(false).Section("StatsHelper").KeysHash() 62 | } 63 | 64 | func (p *StatsHelper) Close() { 65 | } 66 | -------------------------------------------------------------------------------- /plugins/TpsPlugin.go: -------------------------------------------------------------------------------- 1 | package plugin 2 | 3 | import ( 4 | "MCDaemon-go/command" 5 | "MCDaemon-go/lib" 6 | "strconv" 7 | "time" 8 | ) 9 | 10 | type TpsPlugin struct{} 11 | 12 | func (hp *TpsPlugin) Handle(c *command.Command, s lib.Server) { 13 | if len(c.Argv) == 0 { 14 | c.Argv = append(c.Argv, "help") 15 | } 16 | if second, ok := strconv.Atoi(c.Argv[0]); ok == nil { 17 | if second > 30 { 18 | second = 30 19 | } 20 | s.Execute("debug start") 21 | time.Sleep(time.Second * time.Duration(second)) 22 | s.Execute("debug stop") 23 | } else if c.Argv[0] == "res" { 24 | s.Say(c.Argv[1][:len(c.Argv[1])-1]) 25 | } else { 26 | text := "使用 !!tps [秒数] 指定获取多少秒内的tps(自动限幅30s内)" 27 | s.Tell(c.Player, text) 28 | } 29 | } 30 | 31 | func (hp *TpsPlugin) Init(s lib.Server) { 32 | } 33 | 34 | func (hp *TpsPlugin) Close() { 35 | } 36 | -------------------------------------------------------------------------------- /plugins/WarnPlugin.go: -------------------------------------------------------------------------------- 1 | /* 2 | * 转发服务器警告消息,并在接收到fatal时自动重启服务器进程 3 | * author: Sciroccogti 4 | */ 5 | 6 | package plugin 7 | 8 | import ( 9 | "MCDaemon-go/command" 10 | "MCDaemon-go/lib" 11 | "fmt" 12 | "strconv" 13 | //"time" 14 | ) 15 | 16 | type WarnPlugin struct{} 17 | 18 | func (wp *WarnPlugin) Handle(c *command.Command, s lib.Server) { 19 | if len(c.Argv) != 3 { 20 | c.Argv[0] = "help" 21 | } 22 | 23 | switch c.Argv[0] { 24 | case "warn": 25 | ticks, _ := strconv.Atoi(c.Argv[2]) 26 | if ticks >= 40 && ticks < 60 { 27 | s.Say(command.Text{fmt.Sprintf("嗯?服务姬有点忙不过来了,延迟%dticks~", ticks), "gray"}) 28 | s.WriteLog("info", fmt.Sprintf("服务器延迟%dticks", ticks)) 29 | } else if ticks >= 60 && ticks < 80 { 30 | s.Say(command.Text{fmt.Sprintf("哎呀呀,让服务姬歇一会吧,延迟%dticks!", ticks), "yellow"}) 31 | s.WriteLog("warn", fmt.Sprintf("服务器延迟%dticks", ticks)) 32 | } else if ticks >= 80 && ticks < 100 { 33 | s.Say(command.Text{fmt.Sprintf("呜呜呜,服务姬受不了了!延迟%dticks!", ticks), "red"}) 34 | s.WriteLog("warn", fmt.Sprintf("服务器延迟%dticks", ticks)) 35 | } else if ticks >= 100 { 36 | s.Say(command.Text{"服务器负载过高!请立即停止活动并联系服主!", "red"}) 37 | s.WriteLog("fatal", fmt.Sprintf("服务器延迟%dticks", ticks)) 38 | //time.Sleep(time.Second * 10) 39 | //s.Restart() 40 | } 41 | default: 42 | } 43 | } 44 | 45 | func (wp *WarnPlugin) Init(s lib.Server) { 46 | } 47 | 48 | func (wp *WarnPlugin) Close() { 49 | } 50 | -------------------------------------------------------------------------------- /plugins/hotPlugin.go: -------------------------------------------------------------------------------- 1 | package plugin 2 | 3 | import ( 4 | "MCDaemon-go/command" 5 | "MCDaemon-go/lib" 6 | "fmt" 7 | "os/exec" 8 | "strings" 9 | ) 10 | 11 | //热加载插件类型 12 | type HotPlugin struct { 13 | string 14 | } 15 | 16 | func (hp *HotPlugin) Handle(c *command.Command, s lib.Server) { 17 | commandName := "./hotPlugins/" + c.PluginName 18 | pluginProcess := exec.Command(commandName, c.Argv...) 19 | buffer, err := pluginProcess.Output() 20 | if err != nil { 21 | s.Tell(c.Player, fmt.Sprint("插件出现错误:", err)) 22 | } 23 | retStr := string(buffer) 24 | /** 25 | 插件返回数据以空格区分参数 26 | 第一个为调用方法名 27 | 第二个为方法参数 28 | 第三个如果有则代表玩家名 29 | */ 30 | argv := strings.Fields(retStr) 31 | if len(argv) >= 2 { 32 | switch argv[0] { 33 | case "say": 34 | s.Say(strings.Join(argv[1:], " ")) 35 | case "tell": 36 | if len(argv) >= 3 { 37 | s.Tell(argv[1], strings.Join(argv[2:], " ")) 38 | } 39 | case "Execute": 40 | s.Execute(strings.Join(argv[1:], " ")) 41 | } 42 | } 43 | } 44 | 45 | func (hp *HotPlugin) Init(s lib.Server) { 46 | } 47 | 48 | func (hp *HotPlugin) Close() { 49 | } 50 | -------------------------------------------------------------------------------- /plugins/pluginList_linux.go: -------------------------------------------------------------------------------- 1 | // +build !windows 2 | 3 | package plugin 4 | 5 | import ( 6 | "MCDaemon-go/plugins/BackupPlugin" 7 | "MCDaemon-go/plugins/ChatPlugin" 8 | ) 9 | 10 | func CreatePluginsList(isload bool) (PluginMap, PluginMap) { 11 | //可使用插件列表 12 | PluginsList := make(PluginMap) 13 | //已被禁用插件列表 14 | DisablePluginsList := make(PluginMap) 15 | 16 | //加载热插件 17 | PluginsList.GetHotPlugins(isload) 18 | 19 | //注册冷插件 20 | PluginsList.RegisterPlugin("!!server", &BasePlugin{}) //基础插件 21 | PluginsList.RegisterPlugin("!!backup", &BackupPlugin.BackupPlugin{}) //备份插件插件 22 | PluginsList.RegisterPlugin("!!yinyinmaster", &Yinyin{}) //例子插件 23 | PluginsList.RegisterPlugin("!!image", &ImagePlugin{}) //镜像插件 24 | PluginsList.RegisterPlugin("!!SDChat", &SDChatPlugin{}) //沙雕聊天机器人插件 25 | PluginsList.RegisterPlugin("!!tps", &TpsPlugin{}) //tps插件 26 | PluginsList.RegisterPlugin("!!Chat", &ChatPlugin.ChatPlugin{}) //跨服聊天插件 27 | PluginsList.RegisterPlugin("!!autobk", &AutoBackup{}) // 自动备份插件 28 | PluginsList.RegisterPlugin("!!Warn", &WarnPlugin{}) // 警告信息插件 29 | PluginsList.RegisterPlugin("!!stats", &StatsHelper{}) // 榜单信息插件 30 | PluginsList.RegisterPlugin("!!here", &HerePlugin{}) // 坐标广播插件 31 | return PluginsList, DisablePluginsList 32 | } 33 | -------------------------------------------------------------------------------- /plugins/pluginList_windows.go: -------------------------------------------------------------------------------- 1 | // +build windows 2 | 3 | package plugin 4 | 5 | import ( 6 | "MCDaemon-go/plugins/BackupPlugin" 7 | "MCDaemon-go/plugins/ChatPlugin" 8 | ) 9 | 10 | func CreatePluginsList(isload bool) (PluginMap, PluginMap) { 11 | //可使用插件列表 12 | PluginsList := make(PluginMap) 13 | //已被禁用插件列表 14 | DisablePluginsList := make(PluginMap) 15 | 16 | //加载热插件 17 | PluginsList.GetHotPlugins(isload) 18 | 19 | //注册冷插件 20 | PluginsList.RegisterPlugin("!!server", &BasePlugin{}) //基础插件 21 | PluginsList.RegisterPlugin("!!backup", &BackupPlugin.BackupPlugin{}) //备份插件插件 22 | PluginsList.RegisterPlugin("!!yinyinmaster", &Yinyin{}) //例子插件 23 | PluginsList.RegisterPlugin("!!image", &ImagePlugin{}) //镜像插件 24 | PluginsList.RegisterPlugin("!!SDChat", &SDChatPlugin{}) //沙雕聊天机器人插件 25 | PluginsList.RegisterPlugin("!!tps", &TpsPlugin{}) //tps插件 26 | PluginsList.RegisterPlugin("!!Chat", &ChatPlugin.ChatPlugin{}) //跨服聊天插件 27 | PluginsList.RegisterPlugin("!!Warn", &WarnPlugin{}) // 警告信息插件 28 | PluginsList.RegisterPlugin("!!stats", &StatsHelper{}) // 榜单信息插件 29 | PluginsList.RegisterPlugin("!!here", &HerePlugin{}) // 坐标广播插件 30 | return PluginsList, DisablePluginsList 31 | } 32 | -------------------------------------------------------------------------------- /server/plugin.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "MCDaemon-go/command" 5 | "MCDaemon-go/config" 6 | "MCDaemon-go/lib" 7 | "fmt" 8 | "strings" 9 | ) 10 | 11 | //运行所有语法解析器 12 | func (svr *Server) RunParsers(word string) { 13 | for _, val := range svr.parserList { 14 | cmd, ok := val.Parsing(word) 15 | if ok && svr.pluginList[cmd.Cmd] != nil { 16 | //异步运行插件 17 | svr.pulginPool <- 1 18 | if cmd.Player != "" { 19 | svr.WriteLog("info", fmt.Sprintf("玩家 %s 运行了 %s %s命令", cmd.Player, cmd.Cmd, strings.Join(cmd.Argv, " "))) 20 | } 21 | go svr.RunPlugin(cmd) 22 | } 23 | } 24 | } 25 | 26 | //运行插件 27 | func (svr *Server) RunPlugin(cmd *command.Command) { 28 | svr.pluginList[cmd.Cmd].Handle(cmd, svr) 29 | <-svr.pulginPool 30 | } 31 | 32 | //等待现有插件的完成并停止后面插件的运行,在执行相关操作 33 | func (svr *Server) RunUniquePlugin(handle func()) { 34 | svr.unqiueLock.Lock() 35 | defer svr.unqiueLock.Unlock() 36 | <-svr.pulginPool 37 | //根据插件最大并发数进行堵塞 38 | maxRunPlugins, _ := config.Cfg.Section("MCDeamon").Key("maxRunPlugins").Int() 39 | for i := 0; i < maxRunPlugins; i++ { 40 | svr.pulginPool <- 1 41 | } 42 | handle() 43 | for i := 0; i < maxRunPlugins; i++ { 44 | <-svr.pulginPool 45 | } 46 | svr.pulginPool <- 1 47 | } 48 | 49 | //获取当前实例的插件列表 50 | func (svr *Server) GetPluginList() map[string]lib.Plugin { 51 | return svr.pluginList 52 | } 53 | 54 | //获取当前实例的禁用插件列表 55 | func (svr *Server) GetDisablePluginList() map[string]lib.Plugin { 56 | return svr.disablePluginList 57 | } 58 | 59 | //获取语法解析器列表 60 | func (svr *Server) GetParserList() []lib.Parser { 61 | return svr.parserList 62 | } 63 | 64 | //调用插件释放资源函数 65 | func (svr *Server) RunPluginClose() { 66 | for _, v := range svr.pluginList { 67 | v.Close() 68 | } 69 | for _, v := range svr.disablePluginList { 70 | v.Close() 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /server/server.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "MCDaemon-go/config" 5 | "MCDaemon-go/container" 6 | "MCDaemon-go/lib" 7 | parser "MCDaemon-go/parsers" 8 | plugin "MCDaemon-go/plugins" 9 | "bufio" 10 | "fmt" 11 | "io" 12 | "log" 13 | "net" 14 | "os/exec" 15 | "strconv" 16 | "sync" 17 | ) 18 | 19 | var err error 20 | 21 | type Server struct { 22 | name string //服务器名称 23 | Stdout *bufio.Reader //子进程输出 24 | Cmd *exec.Cmd //子进程实例 25 | stdin io.WriteCloser //用于关闭输入管道 26 | stdout io.ReadCloser //用于关闭输出管道 27 | lock sync.Mutex //输入管道同步锁 28 | pulginPool chan interface{} //插件池 29 | maxRunPlugins int //插件最大并发数 30 | pluginList plugin.PluginMap //插件列表 31 | disablePluginList plugin.PluginMap //禁用插件列表 32 | parserList []lib.Parser //语法解析器列表 33 | port string //启动服务器端口 34 | unqiueLock sync.Mutex //堵塞插件执行池锁 35 | } 36 | 37 | //根据参数初始化服务器 38 | func (svr *Server) Init(name string, argv []string, workDir string) { 39 | svr.name = name 40 | //创建子进程实例 41 | svr.Cmd = exec.Command("java", argv...) 42 | svr.Cmd.Dir = workDir 43 | svr.stdout, err = svr.Cmd.StdoutPipe() 44 | if err != nil { 45 | log.Fatal(err) 46 | } 47 | //接管子进程输入输出 48 | svr.Stdout = bufio.NewReader(svr.stdout) 49 | svr.stdin, err = svr.Cmd.StdinPipe() 50 | if err != nil { 51 | log.Fatal(err) 52 | } 53 | //初始化插件执行池参数 54 | svr.maxRunPlugins, _ = strconv.Atoi(config.Cfg.Section("MCDeamon").Key("maxRunPlugins").String()) 55 | svr.pulginPool = make(chan interface{}, svr.maxRunPlugins) 56 | 57 | //初始化插件列表 58 | svr.pluginList, svr.disablePluginList = plugin.CreatePluginsList(false) 59 | svr.parserList = parser.CreateParserList() 60 | //执行插件init 61 | for _, v := range svr.pluginList { 62 | v.Init(svr) 63 | } 64 | //设置端口 65 | if svr.port == "" { 66 | svr.port = "25565" 67 | } 68 | } 69 | 70 | //运行子进程 71 | func (svr *Server) run_process() { 72 | svr.Cmd.Start() 73 | } 74 | 75 | func (svr *Server) Getinfo() string { 76 | return fmt.Sprintf("镜像名称:%s\\n,端口:%s\\n", svr.name, svr.port) 77 | } 78 | 79 | //写入日志 80 | func (svr *Server) WriteLog(level string, msg string) { 81 | lib.WriteRuntimeLog(level, msg, svr.name) 82 | } 83 | 84 | //重启服务器 85 | func (svr *Server) Restart() { 86 | c := container.GetInstance() 87 | c.Group.Add(1) 88 | //关闭 89 | c.Del(svr.name) 90 | //启动 91 | workDir := svr.Cmd.Dir 92 | c.Add(svr.name, workDir, svr) 93 | c.Group.Done() 94 | } 95 | 96 | //启动服务器 97 | func (svr *Server) Start(name string, Argv []string, workDir string) { 98 | //初始化 99 | svr.Init(name, Argv, workDir) 100 | //等待加载地图 101 | if svr.WaitEndLoading() { 102 | //正式运行MCD 103 | svr.Run() 104 | } else { 105 | //没加载成功就释放同步锁 106 | c := container.GetInstance() 107 | c.Group.Done() 108 | } 109 | } 110 | 111 | //重新读取配置文件 112 | func (svr *Server) ReloadConf() { 113 | svr.pluginList, svr.disablePluginList = plugin.CreatePluginsList(true) 114 | } 115 | 116 | //复制一个镜像服务器(用于镜像启动) 117 | func (svr *Server) Clone() lib.Server { 118 | cloneServer := &Server{} 119 | // 得到一个可用的端口 120 | port, _ := func() (string, bool) { 121 | listener, err := net.Listen("tcp", "127.0.0.1:0") 122 | if err != nil { 123 | return "", false 124 | } 125 | defer listener.Close() 126 | addr := listener.Addr().String() 127 | _, portString, err := net.SplitHostPort(addr) 128 | if err != nil { 129 | return "", false 130 | } 131 | return portString, true 132 | }() 133 | cloneServer.port = port 134 | return cloneServer 135 | } 136 | 137 | //获取端口 138 | func (svr *Server) GetPort() string { 139 | return svr.port 140 | } 141 | 142 | //获取服务器实例名称//获取端口 143 | func (svr *Server) GetName() string { 144 | return svr.name 145 | } 146 | 147 | //以容器形式关闭服务器 148 | func (svr *Server) CloseInContainer() { 149 | c := container.GetInstance() 150 | //关闭 151 | c.Del(svr.name) 152 | } 153 | 154 | //关闭服务器 155 | func (svr *Server) Close() { 156 | // 关闭插件 157 | svr.RunPluginClose() 158 | svr.Execute("/stop") 159 | } 160 | 161 | //在容器中注销该服务器 162 | func (svr *Server) End() { 163 | // 关闭插件 164 | svr.RunPluginClose() 165 | //释放同步锁 166 | c := container.GetInstance() 167 | c.Group.Done() 168 | } 169 | -------------------------------------------------------------------------------- /server/stdin.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "MCDaemon-go/command" 5 | "fmt" 6 | "io" 7 | ) 8 | 9 | func (svr *Server) Say(argv ...interface{}) { 10 | svr.Tell("@a", argv...) 11 | } 12 | 13 | func (svr *Server) Tell(player string, argv ...interface{}) { 14 | var stringText string 15 | var TextArray []command.Text 16 | var _command string 17 | for _, v := range argv { 18 | switch t := v.(type) { 19 | case string: 20 | stringText = t 21 | case []command.Text: 22 | TextArray = append(TextArray, t...) 23 | case command.Text: 24 | TextArray = append(TextArray, t) 25 | default: 26 | fmt.Println("不支持的消息类型") 27 | } 28 | } 29 | if stringText != "" { 30 | _command = fmt.Sprintf("/tellraw %s {\"text\":\"%s\"}", player, stringText) 31 | } else if len(TextArray) != 0 { 32 | _command, _ = command.JsonEncode(TextArray) 33 | _command = fmt.Sprintf("/tellraw %s %s", player, _command) 34 | } 35 | svr.Execute(_command) 36 | } 37 | 38 | func (svr *Server) Execute(_command string) { 39 | //输入的命令要换行!否则无法执行 40 | _command = _command + "\n" 41 | //同步写入 42 | svr.lock.Lock() 43 | defer svr.lock.Unlock() 44 | _, err := io.WriteString(svr.stdin, _command) 45 | if err != nil { 46 | // fmt.Println("there is a error!", err) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /server/stdout.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "fmt" 7 | "io/ioutil" 8 | "os" 9 | "runtime" 10 | "strings" 11 | "unicode/utf8" 12 | 13 | "golang.org/x/text/encoding/simplifiedchinese" 14 | "golang.org/x/text/transform" 15 | ) 16 | 17 | //等待服务器加载完地图 18 | func (svr *Server) WaitEndLoading() bool { 19 | var buffer []byte = make([]byte, 4096) 20 | var retStr string 21 | //运行子进程 22 | svr.run_process() 23 | fmt.Println("正在加载服务器地图...") 24 | for { 25 | n, err := svr.Stdout.Read(buffer) 26 | retStr = string(buffer[:n]) 27 | if err != nil { 28 | fmt.Println(svr.name, "服务器启动失败!") 29 | return false 30 | } 31 | if strings.Contains(retStr, "[Server thread/INFO]: Done") { 32 | fmt.Println(svr.name, "服务器地图加载完成!") 33 | break 34 | } 35 | } 36 | return true 37 | } 38 | 39 | //正式运行MCD 40 | func (svr *Server) Run() { 41 | var buffer []byte = make([]byte, 102400) 42 | var buffercmd []byte = make([]byte, 4096) 43 | cmdin := bufio.NewReader(os.Stdin) 44 | go func() { 45 | for { 46 | n, err := cmdin.Read(buffercmd) 47 | if err != nil { 48 | break 49 | } 50 | cmdStr := Buffer2String(buffercmd, n) 51 | if cmdStr[len(cmdStr)-1] == '\n' { 52 | cmdStr = cmdStr[0 : len(cmdStr)-1] 53 | } 54 | svr.Execute(cmdStr) 55 | } 56 | }() 57 | 58 | for { 59 | n, err := svr.Stdout.Read(buffer) 60 | if err != nil { 61 | //如果进程已关闭则执行容器关闭函数 62 | svr.End() 63 | break 64 | } 65 | svrStr := Buffer2String(buffer, n) 66 | 67 | fmt.Println(svr.name, "服务器:", svrStr) 68 | // 异步处理语法解析器和运行插件 69 | go svr.RunParsers(svrStr) 70 | } 71 | 72 | } 73 | 74 | // Buffer2String :从内存读取字符串 75 | func Buffer2String(buffer []byte, n int) string { 76 | var retStr string 77 | //注意在window下minecraft进程间通信使用gbk2312; 78 | //而在linux下则是utf-8 79 | switch runtime.GOOS { 80 | case "darwin", "linux": 81 | var tempN int 82 | var tempStr rune 83 | for i := 0; i < n; { 84 | tempStr, tempN = utf8.DecodeRune(buffer[i:]) 85 | retStr += fmt.Sprintf("%c", tempStr) 86 | i += tempN 87 | } 88 | case "windows": 89 | reader := transform.NewReader(bytes.NewReader(buffer[:n]), simplifiedchinese.GBK.NewDecoder()) 90 | retBt, _ := ioutil.ReadAll(reader) 91 | retStr = string(retBt) 92 | } 93 | return retStr 94 | } 95 | -------------------------------------------------------------------------------- /start.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "MCDaemon-go/config" 5 | "MCDaemon-go/container" 6 | "MCDaemon-go/server" 7 | ) 8 | 9 | var ( 10 | MCDeamon []string 11 | ) 12 | 13 | func init() { 14 | //配置eula文件 15 | config.SetEula() 16 | //获取所有启动项配置 17 | MCDeamon = config.GetStartConfig() 18 | } 19 | 20 | func main() { 21 | c := container.GetInstance() 22 | defaultServer := &server.Server{} 23 | //加入到容器中并开启服务器 24 | c.Add("default", config.Cfg.Section("MCDeamon").Key("server_path").String(), defaultServer) 25 | c.Group.Wait() 26 | } 27 | --------------------------------------------------------------------------------