Spigot插件开发笔记——第一个插件与事前准备

一、准备工作

配置开发环境

官网下载
JAVA开发环境—JDK下载、安装与配置环境变量

选择编译工具

IDEA

收费软件,目前公认最好用的Java开发工具,售价颇高,但学生可以通过学生认证申请授权,授权有效期间JetBrains下所有新版本开发工具皆可免费下载使用
能够装载许多功能强大的插件,大部分免费,少部分收费
本文不提供破解方法,请自行搜索破解手段,若有能力请尽量支持正版

Eclipse

免费开源的JavaIDE,使用者较多。能够装载插件以扩展使用场景。
比起IDEA而言,对电脑性能的需求更低
许多方向相较IDEA皆有优有劣,区别相当大的一项是智能提示与补全功能的区别,不同人有不同的使用体验,在此不再赘述

Notepad++

免费软件,与其说是编译工具,倒不如说是功能更加强大的记事本,而软件的名字也恰是如此
有完整的中文化接口及支持多国语言编写的功能(UTF8技术)
功能比 Windows 中的 Notepad(记事本)强大许多,除了可以用来制作一般的纯文字说明文件,也十分适合编写计算机程序代码。不仅有语法高亮度显示,也有语法折叠功能,并且支持宏以及扩充基本功能的外挂模组
尽管软件免费开源且相当好用,但该软件作者牵扯到政治的一些行为相当离谱,请自行搜索了解后决定是否使用

除以上各软件外,还有NetbeansMyEclipse等编译工具,但使用者较少,便不再介绍

获取Spigot服务端核心

你需要获取最新版本的BuildTools以下载自己所需版本的Spigot核心,在上方链接打开的Jenkins页面上点击最终成功构建字样下方的BuildTools.jar点击这里直接下载最新版本的BuildTools
下载成功后,在BuildTools.jar文件放置的路径下打开cmd控制台
注1:请先把BuildTools文件转移到合适的路径下
注2:在指定文件夹下打开命令行

输入java -jar BuildTools.jar以获取最新版本的Spigot核心
输入java -jar BuildTools.jar --rev 版本号以获取指定版本的Spigot核心
例:java -jar BuildTools.jar --rev 1.13.2可获取1.13.2版本

相关链接

SpigotMC官网
Spigot-API

二、新建插件

以下步骤使用编译工具为IDEA,由于加载了一些改变UI的插件,并且使用的是旧版本IDEA,因此截图中的图标、位置等信息与实际情况未必一致,请根据文字描述与实际情况来操作

新建Java项目

打开IDEA,选择Create New Project以进入建立新Java项目的界面

新建项目 - 打开方式一

若你已新建、打开,或自动启动了其他项目,则在上方工具栏中依次点选File -> New -> Project,亦可进入同样的界面

新建项目 - 打开方式二

在左侧列表选择Java,右侧注意顶部的ProjectSDK,若此处未能读取你之前安装的JDK,可以点击New按钮手动选择路径,除此之外不需要做其他操作,点击下方的NEXT按钮

新建项目 - 选择Java、设置SDK

这页不用操作,直接NEXT

新建项目 - 下一步

给你的项目选一个路径,再取个合适的名字,比如,SpigotDemo,然后点击FINISH按钮

新建项目 - 完成

注:若你选择的路径下没有对应名字的文件夹,这一步则会弹出窗口询问你是否新建文件夹,点击确定即可

新建项目 - 效果图

到这里,一个空的Java项目便创建好了,但在开始编写插件之前,还需要做一些准备

完善文件夹结构

右击项目根目录[SpigotDemo],依次点击New -> Directory,在弹出的窗口中输入lib,点击ok

完善结构 - 创建lib文件夹

这一步新建一个文件夹以便于存放后续操作中将会用到的文件,你也可以取成其他的名字。这一步是可以跳过的,但出于各种方面的原因,我都不建议这么做。

右击src文件夹,依次点击New -> Package,输入main,点击ok
右击src文件夹,依次点击New -> Package,输入resources,点击ok
在这一步中新建的两个文件夹分别是存放代码主体的主文件夹和存放配置文件的资源文件夹,执行完以上新建后,文件夹构造如下图所示

完善结构 - 创建main与resources文件夹

仅仅是新建文件夹可还不够,你还需要将这两个文件夹标记一下
右击src文件夹,依次点击Mark Directory as -> Unmark as Sources Root
在少数版本的IDEA里,或是根据新建项目的方式不同,有时新项目并不会将src文件夹标为默认源文件夹,如果在Mark Directory as中没有看到Unmark as Sources Root,则跳过这一步
右击main文件夹,依次点击Mark Directory as -> Sources Root
右击resources文件夹,依次点击Mark Directory as -> Resources Root
至此,文件夹标记完毕,文件夹构造虽然没有变化,但你或许可以看到图标发生了些微改变。这里就不再截图展示了。
到这一步为止,完善文件夹结构的操作便结束了,接下来要做的则是准备必需的文件

准备文件

插件部分

右击main文件夹,依次点击New -> Java Class,在弹出的窗口中输入Main,点击OK
右击resources文件夹,依次点击New -> File,在弹出的窗口中输入plugin.yml,点击OK
将准备工作中下载好的Spigot核心复制到lib文件夹下,复制粘贴或者拖动皆可
完成上述操作后,文件排布应如下所示

准备文件 - 新建Main与plugin.yml文件并准备Spigot核心

依次点击上方工具栏中的File -> Project Structure,打开Project Structure页面

准备文件 - 打开项目结构页面

在该页面左侧选择Libraries标签,依次点击+ -> Java

准备文件 - 导入Spigot核心到项目库

在弹出的页面里找到你Spigot核心的存放路径,若已经移至lib文件夹下,则直接选择lib文件夹下的Spigot核心,点击OK。
准备文件 - 选择路径

在之前若未创建lib文件夹,则直接从你存放的路径中寻找,注意,在这里选择完Spigot核心后,如果Spigot核心的文件存放位置发生改变,则需要重新上述步骤进行进行再设置

准备文件 - 选择导入项目

在弹出的窗口中选择自己的项目,点击OK
准备文件 - 导入成功效果图

添加完成后该页面如上图所示,点击右下角的APPLY按钮保存一下上述的操作,先不急着关闭页面,直接开始下一步
点击左侧的Artifacts标签,依次点击+ -> jar -> Empty
准备文件 - Artifacts

准备文件 - Artifacts -> Add -> jar -> empty

在Name栏中输入一个名字,这个名字将成为你打包后的文件名
双击最右侧的'SpigotDemo' compile output使其移到左侧
其他地方不需要修改,点击OK

准备文件 - 准备完成效果图

至此,插件部分准备完毕,在此之外还需要准备一份可以正常运行的服务端

服务端部分

这一步其实就等同于开服,过程并不复杂,文字描述足矣,便不再附图

将Spigot核心文件拷贝到适合的空文件夹下,在该文件夹中打开命令行
输入java -jar spigot-1.16.5.jar -nogui,回车
其中spigot-1.16.5.jar为你的Spigot核心文件的文件名,若为其他版本核心,或被重命名成其他文件名,请根据实际情况填写
如果按照步骤来进行,初次运行是必定失败,失败时会返回如下提示
You need to agree to the EULA in order to run the server. Go to eula.txt for more info.
翻译:"您需要同意EULA才能运行服务器。有关详细信息,请转到eula.txt。"
服务端运行时,会自动生成eula.txt文件与server.properties文件,你需要把eula.txt文件中eula=false改为eula=true,这一操作意为"同意MINECRAFT最终用户许可协议",是启动服务端的必要项
一般而言server.properties不需要进行修改也能启动服务端,但出于各种各样的原因(非正版玩家、网络异常无法正版登录、便于离线调试插件等),你可以把该文件中online-mode=true改为online-mode=false以启用离线模式
再次打开命令行,输入java -jar spigot-1.16.5.jar -nogui,回车,等待少许时间,世界生成结束,输入help可以看到指令提示时,则说明启动已经成功了
你可以看到服务端文件夹里生成了一个plugins文件夹,这就是你写好的插件将要放置的地方
至此,服务端部分也准备完毕

提示

1、虽然部分版本由于改动较小,可以跨几个小版本运作,但依然建议在开发插件和开服时使用同一版本的服务端核心
2、若第一次启动服务端时未能自动生成文件,请检查java的安装与配置是否完成,若已完成,则将服务端文件移出C盘或使用管理员权限打开命令行
3、你可以创建bat文件来启动服务器,而不需要每次都打开命令行,方法是新建一个.txt文件,输入java -jar spigot-1.16.5.jar -nogui,保存,将.txt文件的后缀修改为.bat,之后便可以双击bat文件直接启动服务端了

开始编写你的插件!

一个完全空白的插件

编辑plugin.yml文件
依照下方的格式适当修改成你自己想要的样子,或者直接复制粘贴也可以
'#'号开头的行是注释行,直接删除也不会影响这个配置文件发挥作用

#插件名称
name: SpigotDemo
#主类
main: Main
#插件版本
version: 0.0.1
#核心版本
api-version: 1.16
#作者
author: Delamer
#注册指令
commands:
  #只有被注册的指令才能够正常使用
  #请避免和现有指令重复
  demo:
    description: 这一行填写指令的介绍
    usage: 这一行填写指令的用法

修改完后直接转到Main,不用担心保存问题,IDEA会自动保存你修改的内容
新建的空白Main文件一开始应该是这个亚子的

public class Main {
}

Main字样后方,花括号前方键入extends JavaPlugin,在输入到一半的自动补全列表中选择JavaPlugin以自动导入对应的包,或是在将该内容完整输入后,使用Alt + Enter快捷键导入对应的包,成功导入该文件内容后如下所示

import org.bukkit.plugin.java.JavaPlugin;

public class Main extends JavaPlugin {

}

一个插件通常由主线程,执行器,监听器,配置文件等许多部件组成,其中主线程,配置文件是必不可少的部件,其他部件则视情况添加,现在的Main文件继承了JavaPlugin,则表示这个文件将是整个插件的主线程,而plugin.yml则是必需的配置文件,到这里为止,一个清清白白的没有任何功能的插件就编写完毕了,试着打包一下看看
依次点击上方工具栏Build -> Build Artifacts

编写插件 - 打包

在弹出的窗口周选择SpigotDemo -> Build
编写插件 - 打包

短暂的等待后,你的项目下应该会多出一个out文件夹,你的第一个插件现在就在这个文件夹里
打开路径out/artifacts/SpigotDemo,可以看到一个SpigotDemo.jar文件
编写插件 - 打包完成

把这个文件拷贝到你服务端的plugins文件夹下,试着运行一下服务端看看
编写插件 - 试运行效果图

看到这条提示就意味着启动成功了,尽管没有任何功能,但这确确实实是一个可以被服务端执行的插件
接下来,让我们试着添加一些功能吧

给提示增加一点色彩

在刚在的试启动中,这个启动成功的提示太不起眼了,让我们增加一些提示,使它变得扎眼一些
Main中增加onLoad,onEnable,onDisable方法,这些是JavaPlugin中本来就有的方法,重写这些方法的时候最好在前方加上@Override注释。当然不加也不影响运行。

    @Override
    public void onLoad() {
        Bukkit.getConsoleSender().sendMessage("§f<§5SpigotDemo§f>§e插件加载了");
    }

    @Override
    public void onEnable() {
        Bukkit.getConsoleSender().sendMessage("§f<§5SpigotDemo§f>§e插件启动了");
    }

    @Override
    public void onDisable() {
        Bukkit.getConsoleSender().sendMessage("§f<§5SpigotDemo§f>§e插件关闭了");
    }

以上三个方法分别执行于插件加载时,插件启动时,插件关闭时,其中Bukkit.getConsoleSender().sendMessage();的效果则是在控制台输出消息。
不同于System.out.println();,这个方法可以使用Minecraft内置的颜色代码来修改输出格式。
重新打包,覆盖plugins下的旧插件,再运行,效果如下

编写插件 - 试运行效果

编写插件 - 试运行效果

你可以打包运行试试效果,也可以直接进行下一步,不过§5这个颜色似乎有点暗,那便先做一个指令显示一下所有颜色的代码,然后换一个亮一点的代码吧。

建立执行器

src/main路径下新建一个类,命名为Executor
(即上文中的 [右击main文件夹,依次点击New -> Java Class,在弹出的窗口中输入Executor,点击OK] 这一系列操作 )
使类实现接口CommandExecutor,并补充必须实现的方法onCommand,如下所示

import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;

public class Executor implements CommandExecutor {
    @Override
    public boolean onCommand(CommandSender sender, Command command, String str, String[] args) {
        return true;
    }
}

修改onCommand方法内的反馈,使我们在输入指令触发该方法时,返回一串文字,比如,改成这样

public boolean onCommand(CommandSender sender, Command command, String str, String[] args) {
    sender.sendMessage("§00§11§22§33§44§55§66§77§88§99§aa§bb§cc§dd§ee§ff");
    return true;
}

接下来,回到Main文件,在onEnable方法中添加Bukkit.getPluginCommand("demo").setExecutor(new Executor());,之后便可以在输入指令demo时触发在执行器中编辑的方法,试试看:

编写插件 - 试运行效果

执行成功。

本阶段代码:
Main.java

import org.bukkit.Bukkit;
import org.bukkit.plugin.java.JavaPlugin;

public class Main extends JavaPlugin {
    @Override
    public void onLoad() {
        Bukkit.getConsoleSender().sendMessage("§f<§aSpigotDemo§f>§e插件加载了");
    }

    @Override
    public void onEnable() {
        //绑定指令与执行器
        //注:此处绑定的指令必须要先在plugin.yml中注册
        Bukkit.getPluginCommand("demo").setExecutor(new Executor());
        Bukkit.getConsoleSender().sendMessage("§f<§aSpigotDemo§f>§e插件启动了");
    }

    @Override
    public void onDisable() {
        Bukkit.getConsoleSender().sendMessage("§f<§aSpigotDemo§f>§e插件关闭了");
    }

}

Executor.java

import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;

public class Executor implements CommandExecutor {
    @Override
    public boolean onCommand(CommandSender sender, Command command, String str, String[] args) {
        sender.sendMessage("§00§11§22§33§44§55§66§77§88§99§aa§bb§cc§dd§ee§ff");
        return true;
    }
}
建立监听器

src/main路径下新建一个类,命名为Monitor
使类实现接口Listener,如下所示

import org.bukkit.event.Listener;

public class Monitor implements Listener {
}

改点什么好呢,我看这个默认的上下线提示怪难看的,就先改它吧!
原上线提示:


编写插件 - 原上线提示

新上线提示:


编写插件 - 新上线提示

话不多说,直接贴代码
    @EventHandler
    public void onPlayerJoinEvent(PlayerJoinEvent e) {
        //清除原有的上线提示
        e.setJoinMessage(null);
        //以广播的形式发送新的上线提示
        Bukkit.broadcastMessage("§e玩家§f " + e.getPlayer().getName() + " §e上线了");
    }

塞进Monitor即可,下线提示同理

    @EventHandler
    public void onPlayerQuitEvent(PlayerQuitEvent e) {
        //清除原有的下线提示
        e.setQuitMessage(null);
        //以广播的形式发送新的下线提示
        Bukkit.broadcastMessage("§e玩家§f " + e.getPlayer().getName() + " §e下线了");
    }

添加完毕后,在Main中还需要添加一行代码以注册这个编写完成的监听器
Bukkit.getPluginManager().registerEvents(new Monitor(), this);
效果:

编写插件 - 运行效果

本阶段代码:
Main.java

import org.bukkit.Bukkit;
import org.bukkit.plugin.java.JavaPlugin;

public class Main extends JavaPlugin {
    @Override
    public void onLoad() {
        Bukkit.getConsoleSender().sendMessage("§f<§aSpigotDemo§f>§e插件加载了");
    }

    @Override
    public void onEnable() {
        //绑定指令与执行器
        //注:此处绑定的指令必须要先在plugin.yml中注册
        Bukkit.getPluginCommand("demo").setExecutor(new Executor());
        //注册监听器
        Bukkit.getPluginManager().registerEvents(new Monitor(), this);
        Bukkit.getConsoleSender().sendMessage("§f<§aSpigotDemo§f>§e插件启动了");
    }

    @Override
    public void onDisable() {
        Bukkit.getConsoleSender().sendMessage("§f<§aSpigotDemo§f>§e插件关闭了");
    }

}

Monitor.java

import org.bukkit.Bukkit;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerQuitEvent;

public class Monitor implements Listener {
    @EventHandler
    public void onPlayerJoinEvent(PlayerJoinEvent e) {
        //清除原有的上线提示
        e.setJoinMessage(null);
        //以广播的形式发送新的上线提示
        Bukkit.broadcastMessage("§e玩家§f " + e.getPlayer().getName() + " §e上线了");
    }

    @EventHandler
    public void onPlayerQuitEvent(PlayerQuitEvent e) {
        //清除原有的下线提示
        e.setQuitMessage(null);
        //以广播的形式发送新的下线提示
        Bukkit.broadcastMessage("§e玩家§f " + e.getPlayer().getName() + " §e下线了");
    }
}
现阶段结构与代码展示

结构展示:

编写代码 - 结构展示

代码展示:
Main.java

import org.bukkit.Bukkit;
import org.bukkit.plugin.java.JavaPlugin;

public class Main extends JavaPlugin {
    @Override
    public void onLoad() {
        Bukkit.getConsoleSender().sendMessage("§f<§aSpigotDemo§f>§e插件加载了");
    }

    @Override
    public void onEnable() {
        //绑定指令与执行器
        //注:此处绑定的指令必须要先在plugin.yml中注册
        Bukkit.getPluginCommand("demo").setExecutor(new Executor());
        //注册监听器
        Bukkit.getPluginManager().registerEvents(new Monitor(), this);
        Bukkit.getConsoleSender().sendMessage("§f<§aSpigotDemo§f>§e插件启动了");
    }

    @Override
    public void onDisable() {
        Bukkit.getConsoleSender().sendMessage("§f<§aSpigotDemo§f>§e插件关闭了");
    }

}

Executor.java

import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;

public class Executor implements CommandExecutor {
    @Override
    public boolean onCommand(CommandSender sender, Command command, String str, String[] args) {
        sender.sendMessage("§00§11§22§33§44§55§66§77§88§99§aa§bb§cc§dd§ee§ff");
        return true;
    }
}

Monitor.java

import org.bukkit.Bukkit;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerQuitEvent;

public class Monitor implements Listener {
    @EventHandler
    public void onPlayerJoinEvent(PlayerJoinEvent e) {
        //清除原有的上线提示
        e.setJoinMessage(null);
        //以广播的形式发送新的上线提示
        Bukkit.broadcastMessage("§e玩家§f " + e.getPlayer().getName() + " §e上线了");
    }

    @EventHandler
    public void onPlayerQuitEvent(PlayerQuitEvent e) {
        //清除原有的下线提示
        e.setQuitMessage(null);
        //以广播的形式发送新的下线提示
        Bukkit.broadcastMessage("§e玩家§f " + e.getPlayer().getName() + " §e下线了");
    }
}

三、附录

野路子—单文件实现全功能

或许有些人不喜欢为了寥寥两三种功能新建那么多的文件,实际上主线程内也可以同时包含执行器、监听器、补全器等等内容,只需要将Main的代码改成下面这样的形式,则无需新建ExecutorMonitor文件亦能实现上面的所有功能
注:该情况仅适用于功能较少的场景,否则行数过多代码难以翻阅和修改

import org.bukkit.Bukkit;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import org.bukkit.plugin.java.JavaPlugin;

public class Main extends JavaPlugin implements CommandExecutor, Listener {
    @Override
    public void onLoad() {
        Bukkit.getConsoleSender().sendMessage("§f<§aSpigotDemo§f>§e插件加载了");
    }

    @Override
    public void onEnable() {
        //绑定指令与执行器
        //注:此处绑定的指令必须要先在plugin.yml中注册
        Bukkit.getPluginCommand("demo").setExecutor(this);//单文件编写插件时,此处直接指向本体
        //注册监听器
        Bukkit.getPluginManager().registerEvents(this, this);//单文件编写插件时,此处直接指向本体
        Bukkit.getConsoleSender().sendMessage("§f<§aSpigotDemo§f>§e插件启动了");
    }

    @Override
    public void onDisable() {
        Bukkit.getConsoleSender().sendMessage("§f<§aSpigotDemo§f>§e插件关闭了");
    }

    @Override
    public boolean onCommand(CommandSender sender, Command command, String str, String[] args) {
        sender.sendMessage("§00§11§22§33§44§55§66§77§88§99§aa§bb§cc§dd§ee§ff");
        return true;
    }

    @EventHandler
    public void onPlayerJoinEvent(PlayerJoinEvent e) {
        //清除原有的上线提示
        e.setJoinMessage(null);
        //以广播的形式发送新的上线提示
        Bukkit.broadcastMessage("§e玩家§f " + e.getPlayer().getName() + " §e上线了");
    }
    
    @EventHandler
    public void onPlayerQuitEvent(PlayerQuitEvent e) {
        //清除原有的下线提示
        e.setQuitMessage(null);
        //以广播的形式发送新的下线提示
        Bukkit.broadcastMessage("§e玩家§f " + e.getPlayer().getName() + " §e下线了");
    }
}

善用SpigotAPI中文文档

你无法在网上找到自己所想要实现的所有效果或是相应的教程,但Spigot支持的一切方法都被列在了文档中,这篇文档的汉化程度相当高,翻阅起来相当方便。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,001评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,210评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,874评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,001评论 1 291
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,022评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,005评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,929评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,742评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,193评论 1 309
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,427评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,583评论 1 346
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,305评论 5 342
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,911评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,564评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,731评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,581评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,478评论 2 352

推荐阅读更多精彩内容